This is the blog post release for gtk4-rs 0.8 and gtk-rs-core 0.19.

Although this release happened a few months ago, we finally had time to finish this release blog post!

This is a smaller release than usual, bringing some nice quality of life improvements.

Enjoy!

gtk3 §

Support for gtk3-rs crates was dropped. No more updates will be done on it. If you didn’t yet, please migrate to gtk4-rs.

gtk-rs-core §

Removal of glib channels §

In this release the glib::MainContext::channel() function was removed, together with the corresponding sender/receiver types. In many cases this API has led to overly complicated code in applications because people tried to develop state machines via callbacks, instead of simply making use of async Rust for that purpose.

Instead of using the main context channel, the new way of passing values between other threads and the main thread is by using any of the many async channel implementations. Examples for this are the async-channel crate, the MPSC channels from tokio or async-std, the task/thread join handles of both, the flume crate, …

For example, the following code:

let (sender, receiver) = glib::MainContext::channel(glib::Priority::DEFAULT);
receiver.attach(Some(&glib::MainContext::default()), move |msg| do_things(msg));

sender.send(MyMessage);

Could be rewritten like this with the async-channel crate:

let (sender, receiver) = async_channel::unbounded();
glib::MainContext::default().spawn_local(async move {
    while let Ok(msg) = receiver.recv().await {
        do_things(msg);
    }
});

sender.send_blocking(MyMessage).expect("Channel closed");

Removal of re-exported once_cell crate, use std::cell::OnceCell / std::sync::OnceLock §

If you need lazy initialization then once_cell::sync::Lazy is still useful until LazyCell / LazyLock from std are finalized.

Re-organized traits in glib §

If you get a compiler error because of missing traits, usually the solution would be to make sure that the prelude of the crates (e.g. gtk::prelude::*) is imported.

Dynamic types support §

Let’s say you want to create a plugin to add some features or to customize some application. object_class_dynamic, object_interface_dynamic , enum_dynamic and flags_dynamic are macro helper attributes to make your types dynamic.

// My object types
#[derive(Default)]
pub struct MyType;

#[glib::object_subclass]
#[object_subclass_dynamic]
impl ObjectSubclass for MyType { ... }

// My interfaces
pub struct MyInterface {
    parent: glib::gobject_ffi::GTypeInterface,
}

#[glib::object_interface]
#[object_interface_dynamic]
unsafe impl ObjectInterface for MyInterface { ... }

// My enums
#[derive(Debug, Copy, Clone, PartialEq, Eq, glib::Enum)]
#[enum_type(name = "MyModuleEnum")]
#[enum_dynamic]
enum MyModuleEnum { ... }

// My flags
#[glib::flags(name = "MyFlags")]
#[flags_dynamic]
enum MyFlags { ... }

Your plugin code has to implement the TypePlugin interface or to extend the TypeModule type in order to register or unregister your dynamic types when the module (or plugin) is loaded or unloaded.

#[derive(Default)]
pub struct MyModule;

#[glib::object_subclass]
impl ObjectSubclass for MyModule { ... }

impl ObjectImpl for MyModule {}

impl TypePluginImpl for MyModule {}

impl TypeModuleImpl for MyModule {
    fn load(&self) -> bool {
        // registers my plugin types as dynamic types.
        let my_module = self.obj();
        let type_module: &glib::TypeModule = my_module.upcast_ref();
        MyInterface::on_implementation_load(type_module)
            && MyType::on_implementation_load(type_module)
            && MyEnum::on_implementation_load(type_module)
            && MyFlags::on_implementation_load(type_module)
    }

    fn unload(&self) {
        // marks my plugin types as unregistered.
        let my_module = self.obj();
        let type_module: &glib::TypeModule = my_module.upcast_ref();
        MyFlags::on_implementation_unload(type_module);
        MyEnum::on_implementation_unload(type_module);
        MyType::on_implementation_unload(type_module);
        MyInterface::on_implementation_unload(type_module);
    }
}

By default dynamic types are registered when the system loads your plugin. In some cases, it could be useful to postpone the registration of a dynamic type on the first use. This can be done by setting lazy_registration = true:

// My object types
#[derive(Default)]
pub struct MyType;

#[glib::object_subclass]
#[object_subclass_dynamic(lazy_registration = true)]
impl ObjectSubclass for MyType { ... }

For more complex cases, see the documentation glib::object_subclass, glib::object_interface, glib::Enum, glib::flags, glib::subclass::type_module::TypeModuleImpl and glib::subclass::type_plugin::TypePluginImpl.

gtk4-rs §

  • GTK 4.14 APIs support
  • Support TemplateChild<T> usage with glib::Properties macro, allowing TemplateChild<T> to be used as properties

gtk4-rs:

gtk-rs-core:

All this was possible thanks to the gtk-rs/gir project as well:

Thanks to all of our contributors for their (awesome!) work on this release: