It’s now time for a new gtk-rs release!

As you might’ve noticed, the gtk3-rs projects are getting less and less attention and we intend to deprecate them in one of the future releases. Therefore, we recommend to anyone who didn’t upgrade to GTK4 to do it now. gtk3-rs will get further releases in the foreseeable future to keep up with gtk-rs-core, but no development effort is going to be directed towards it. This is already the de-facto situation for more than a year. Additionally, the GTK3 versions of various externally maintained bindings will most likely not get any further releases.

In addition to gtk-rs, various externally maintained bindings also had a new release. For gstreamer-rs you can find the CHANGELOG of the 0.20 release here. Most bindings maintained as part of GNOME were also updated.

On this note, time to go through the major changes of this release. Enjoy!

Increase of the minimum supported Rust version (MSRV) §

With this release, Rust 1.64 is the minimum version required to compile and use the bindings.

New #[properties] macro to make using properties in glib::Object subclasses simpler §

The glib::Properties macro is now available! It can be used to define object properties straight from the implementation struct definition. The macro will:

  • define all the necessary ParamSpecs, with the required flags
  • generate, for each property, a default getter/setter on the wrapper type
  • generate, for each property, a notify_$property method on the wrapper type
  • generate the methods derived_properties derived_property and derived_set_property, which can be directly called inside your overrides for ObjectImpl methods properties, property and set_property.

Quick example §

#[derive(Properties)]
#[properties(wrapper_type = super::Author)]
pub struct Author {
    #[property(get, set)]
    name: RefCell<String>,
}

This is the result of months of work, and will greatly improve the gtk-rs ecosystem by reducing some of the most prevalent boilerplate and making the platform more welcoming.

Be sure to check out the documentation for more info.

Direct conversions to glib::Value and glib::Variant via From §

Previously conversions required using the custom ToValue and ToVariant traits, which is not very intuitive. Now it’s possible to directly use the From trait, i.e. 123i32.to_value() could become 123i32.into().

In addition to improving usability this also avoids copies of the values in a few cases.

gdk_pixbuf subclassing support §

Support for subclassing various gdk_pixbuf types was added. This allows writing custom pixbuf loaders in Rust and hopefully opens the way for replacing the old C-based PNG/JPEG/GIF pixbuf loaders with Rust versions to improve image loading in all GTK/GNOME applications.

New glib::CastNone convenience trait §

In many cases casting failures of Optional values should be treated as None. For this purpose a new convenience trait was introduced, which provides an and_downcast() method.

let widget: Option<Widget> = list_item.child();

// Without using `CastNone`
let label = widget.unwrap().downcast::<gtk::Label>().unwrap();

// Using `CastNone` we can avoid the first `unwrap()` call
let label = widget.and_downcast::<gtk::Label>().unwrap();

New glib::function_name! macro §

While a huge hack, until Rust supports this directly, this macro will allow to get the name of the current function as a &'static str. Or to be more exact, the whole path to the current function.

This is now used in the GLib structured logging API to provide the function name (in addition to the file name, line number and other metadata) and glib::BoolError to provide better location information.

New API was added to all the aforementioned types to make dealing with them more natural in Rust. Using them instead of the native Rust types, e.g. Vec, allows for fewer copies when passing the FFI boundary.

Various bindings were also switched to these types for the same reason, and it is planned for the next release(s) to move even more API to it.

In addition two new traits were introduced for dealing with GLib strings and string arrays more efficiently: IntoGStr and IntoStrV. Both allow passing a plain &str or an already NUL-terminated &GStr, or a Vec<String> or an already NUL-terminated glib::StrV to functions and internally the function would either do a copy or directly pass the value to the C function. Up to a certain size the copy would by done on the stack, afterwards a temporary heap copy is made. Compared to the before, when temporary heap copies were made unconditionally, this allows for a lot fewer temporary heap allocations.

And lastly, glib::GString is now storing strings up to a certain size inline without requiring a heap allocation. New GStrings can be created via the gformat! macro similar to the format! macro from std.

Various code generation improvements §

All over the bindings, various small code generation improvements were applied which reduced the size of the generated code and/or improved the performance.

For example:

  • Various trivial functions were explicitly marked as #[inline] to give the compiler yet another hint to actually inline them, and improve inlining across crate boundaries, which then causes the bindings to be completely optimized away in most cases.
  • In many places, where it made sense, assertions were converted to debug_assert!s to reduce the number of places where stack unwinding could happen in release builds.
  • The representation of various types was improved, e.g. by removing unnecessary Option wrapping around values or replacing unneeded references with PhantomData with the same lifetime.
  • Memory initialized by C functions was converted from using zero-initialization to MaybeUninit.
  • The number of redundant checks was reduced and unsafe variants of functions that skip the checks were added.
  • All object builder types were changed to use glib::Object::builder(), which reduces stack usage dramatically in addition to requiring fewer value copies.

New glib::Object constructors §

The old Object::new() constructor that took a slice of property name / value pairs was removed. Instead, Object::new() takes no parameters and creates a new object with default property values.

For creating a new object with non-default property values, use Object::builder(). It provides a much nicer API and also allows for better code generation and fewer value copies.

// before
let obj = glib::Object::new::<MyObject>(&[("a", &123i32), ("b", &"some string")]);
// after
let obj = glib::Object::builder::<MyObject>()
    .property("a", 123i32)
    .property("b", "some string")
    .build();

gio::Application::run() returns an ExitCode §

Instead of returning a plain i32, gio::Application::run() now returns an ExitCode type. This can be directly returned from the main() function as it implements the Termination trait, making it much easier to correctly propagate application exit codes.

GTK4 4.10 API additions §

The GTK4 bindings were updated to include all the new APIs introduced by the upcoming GTK 4.10 release. These new APIs are available via the v4_10 feature flag. Note that they are not stable and are subject to change if the underlying C API happens to change before the 4.10.0 release.

The minimum supported GTK version stays at 4.0.0.

Add Blueprint UI definition support §

Blueprint is a new markup language for defining GTK4 UIs. The #[template] macro can now accept Blueprint UI definitions in addition to the old XML-based UI definitions. Doing so requires blueprint-compiler to be available during compilation.

#[derive(Debug, Default, gtk::CompositeTemplate)]
#[template(string = "
template MyWidget : Widget {
    Label label {
        label: 'foobar';
    }
    Label my_label2 {
        label: 'foobaz';
    }
}
")]
pub struct MyWidget {
    #[template_child]
    pub label: TemplateChild<gtk::Label>,
    #[template_child(id = "my_label2")]
    pub label2: gtk::TemplateChild<gtk::Label>,
}

You can also copy the example from https://github.com/gtk-rs/gtk4-rs/releases/tag/0.6.0.

Better mapping of C ownership transfer §

Previously various functions that took ownership of their parameters still used references in the bindings. This required an unnecessary copy to be made in the worst case, and in the best case made it less clear that ownership of the object is given away.

In this release, functions are taking various arguments by value in more places.

Spawning of blocking functions on the glib::MainContext §

gio::spawn_blocking() was added, which allows to spawn blocking functions on an internal threadpool and retrieve their results as a Future from the glib::MainContext. This is useful for doing blocking / CPU intensive work in the background and when the results are available to handle them on the application’s main thread.

As part of this work, JoinHandle-style types were also added to glib::MainContext::spawn() and related functions for spawning Futures on the main context, and to glib::ThreadPool for spawning functions on a custom thread pool and collecting their results.

Changes §

For those who are interested, here is the list of the merged pull requests:

gtk-rs-core:

gtk3-rs:

gtk4-rs:

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: