Hi everyone! Happy new year!

With this new year comes a new gtk-rs release! As usual, a lot of improvements have been made. Let’s check them out!

One important change: the minimum supported rust version is now 1.56.

Minimum and latest supported versions of the underlying C libraries §

The minimum supported versions of the underlying C libraries have not changed compared to last release but support for newer versions was added:

  • GLib/GObject/GIO: minimum 2.48, latest 2.72
  • Pango/PangoCairo: minimum 1.38, latest 1.52
  • gdk-pixbuf: minimum 2.32, latest 2.42
  • cairo: minimum 1.14, latest 1.16
  • graphene: minimum 1.10
  • ATK: minimum 2.18, latest 2.38
  • GTK3: minimum 3.18, latest, 3.24.9
  • GTK4: minimum 4.0, latest 4.6

Changes to derive macro names §

All derive and attribute macros are named more consistently now. Instead of e.g. glib::GBoxed, the new name is just glib::Boxed and misses the additional G, which would be redundant namespacing.

GLib Variant API improvements and derive macro §

The glib::Variant API was improved a lot in this release. Variant is a type that allows to store structured data built from basic types in a way that can be serialized into binary data efficiently.

A lot of API that was missing in previous releases was added, including API for efficient conversion from/to slices of basic types, strings and other container types. The existing conversion APIs were optimized and cleaned up.

To make it easier to handle custom structs (and soon enums), a derive macro was added that allows to directly convert between a custom Rust type and a Variant and back.

use glib::prelude::*;

#[derive(Debug, PartialEq, Eq, glib::Variant)]
struct Foo {
    some_string: String,
    some_int: i32,
}

let v = Foo { some_string: String::from("bar"), some_int: 1 };
let var = v.to_variant();
assert_eq!(var.get::<Foo>(), Some(v));

By using the derive macro, structured data can be handled automatically instead of manually accessing each of the individual parts of the data. This can be helpful for sending structured data over DBus via the gio DBus API, or for dealing with gio::Settings or for more complex states/values used in gio::Action.

Stack allocated types and reducing heap allocations §

Various types that are usually stack-allocated in C were previously heap-allocated in the Rust bindings. With this release, these values are also stack-allocated when used from Rust unless explicitly heap-allocated, e.g. by explicitly placing them into a Box.

Examples for this are all the graphene types (vectors, matrices, etc), which should make calculations considerably more efficient, types like gdk::Rectangle and gdk::RGBA, and gtk::TreeIter.

Overall this brings performance of applications using the Rust bindings even closer to the same code in C.

Main context API improvements §

When spawning non-Send futures or closures, or attaching the main context channel to the glib::MainContext, it is not necessary anymore to have the main context acquired before. This was a common cause of confusion and this constraint was relaxed now. Instead it is only required that that thread default main context is not owned by a different thread yet, and if ownership is moved to a different thread at some point then it will panic at runtime.

Additionally the API for pushing a main context as the thread default was refactored to reduce the risk of misusage. Instead of returning a guard value, it can only be used via a closure now to ensure proper nesting of thread default main contexts.

let ctx = glib::MainContext::new();
ctx.with_thread_default(|| {
    // do something with `ctx` being the thread default main context.
}).expect("Main context already owned by another thread");

GLib string builder §

glib::String was renamed to glib::GStringBuilder to avoid confusion with glib::GString and to make it clearer that this is meant for building NUL-terminated C strings.

In addition the API was simplified considerably:

  • All APIs that allowed to create invalid UTF-8 were removed, leaving only safe APIs to build UTF-8 strings.
  • GStringBuilder directly dereferences into &str, so all &str API can be used on it.
  • GStringBuilder implements the std::fmt::Write trait, which allows making use of various std Rust API. For example write!(&mut builder, "some format string {}", some_value) can be used directly to write formatted strings into the string builder.

Multiple improvements on glib::ObjectExt §

  • ObjectExt::property now return the type T directly instead of a glib::Value and panics in case the property is not readable or the wrong type is provided.
  • ObjectExt::set_property now panics in case the property is not writeable or doesn’t exists
  • ObjectExt::emit_by_name expects you to pass in the type T for the returned value by the signal. If the signal doesn’t return anything, you can use some_obj.emit_by_name::<()>("some_signal", &[]); and also panics if the signal doesn’t exists.

Before:

object.property("some_property").unwrap().get::<T>().unwrap();
object.set_property("some_property", &some_value).unwrap();
// signal that returns nothing
object.emit_by_name("some_signal").unwrap();
// signal that returns T
let some_value = object.emit_by_name("some_signal").unwrap().unwrap().get::<T>().unwrap();

After:

object.property::<T>("some_property");
object.set_property("some_property", &some_value);
object.emit_by_name::<()>("some_signal");
let some_value = object.emit_by_name::<T>("some_signal");

A ObjectExt::try_* variant exists if you want the old behaviour. As most of the application code unwraps the returned results, this should be one of the slight but nice improvements of the overall API.

Bindings wrapper types memory representation optimization §

All heap-allocated bindings types do now have a memory representation that is equivalent to the underlying raw C pointer. In addition to reducing memory usage compared to before, this also allows for other optimizations and e.g allows to borrow values from C storage in a few places instead of creating copies or increasing their reference count (e.g. in glib::PtrSlice).

GLib collection types §

While not used widely yet, a new glib::collections module was added that provides more efficient access to slices, pointer slices and linked lists allocated from C. This is similar to the older glib::GString type, which represents an UTF-8 encoded, NUL-terminated C string allocated with the GLib allocator.

Previously (and still for most API), these were converted from/to native Rust types at the FFI boundary, which often caused unnecessary re-allocations and memory copies. In the next release we’re trying to move even more API to these new collection types for further performance improvements, and for only doing conversions between Rust and C types when actually necessary.

Cancelling futures spawned on the GLib main context §

Spawning futures on the main context now returns the source id. This allows to get access to the underlying glib::Source and to cancel the future before completion.

Cancelling the future by this, or by other API that drops it (e.g. by selecting on two futures and only processing the first that resolves), will cancel all asynchronous operations performed by them at the next opportunity.

Dynamic signal connections §

A new glib::closure and glib::closure_local was added that allows to create a glib::Closure from a Rust closure while automatically unpacking argument glib::Values to their native Rust types and packing the return value.

While this doesn’t sound too exciting, it makes connecting to signals that have no static bindings more convenient.

Before:

obj.connect(
    "notify", false,
    |args| {
        let _obj = args[0].get::<glib::Object>().unwrap();
        let pspec = args[1].get::<glib::ParamSpec>().unwrap();
        println!("property notify: {}", pspec.name());
    });

After:

obj.connect_closure(
    "notify", false,
    glib::closure_local!(|_obj: glib::Object, pspec: glib::ParamSpec| {
        println!("property notify: {}", pspec.name());
    }));

The macros support the same syntax as the glib::clone macro for creating strong/weak references, plus a watch feature that automatically disconnects the closure once the watched object disappears.

Object subclassing improvements §

glib::ParamSpecOverride has now two helper constructors to create an override of a class property or an interface property. For example, overriding the hadjustment property of the gtk::Scrollable interface can be done with ParamSpecOverride::for_interface::<gtk::Scrollable>("hadjustment").

Passing Cairo image surfaces to other threads and getting owned access to its data §

Cairo image surfaces can now be converted to an owned value that behaves like an &mut [u8] and that can be safely sent to different threads. This allows both sending Cairo image surfaces to other threads safely as well as their underlying data, e.g. to fill an image surface from a compressed image via the image crate or saving the contents of an image surface to compressed data from a different thread.

let surface =  cairo::ImageSurface::create(cairo::Format:ARgb32, 320, 240).except("Failed to allocate memory");
// Use Cairo API on the `surface`.
// [...]
let data = surface.take_data().expect("Not the unique owner of the surface");
// Send `data` to another thread or otherwise treat it as a `&mut [u8]`.
// [...]
let surface = data.into_inner();
// Use the `surface` with Cairo APIs again.

This solves one of the common problems when using Cairo in multi-threaded applications.

GIO bindings improvements §

The gio bindings saw many additions and improvements. Among other things it is now possible to implement the gio::Initable interface, the gio::IOExtention API is now available, and the gio::Task bindings have been reworked to address soundness issues. More improvements to the gio::Task bindings are necessary for them to become nice to use from Rust.

Gdkwayland §

In this release, we generated the bindings for gdkwayland. You can now use the brand new gdkwayland crate!

GTK3 child properties §

The GTK3 bindings had API added for generically setting and getting child properties.

This allows to access child properties for which no static bindings are available and performs all checks at runtime instead of at compile time. The API works the same as the API for setting and getting object properties.

gtk4-rs and BuilderScope §

The most important feature that landed on this release is a Rust BuilderScope implementation thanks to one of the awesome contributions of @jf2048. This will allow applications to set callbacks/functions names in the UI file and define those in your Rust code. This only works with the CompositeTemplate macro. See the documentations for how to make use of this new feature.

We also added:

  • Trust upstream return nullability: in other words, gtk4 crates trust the nullable annotations provided in the GIR file which replaces a bunch of returned Option and returns only T nowadays. Please open an issue if you find any regressions.
  • GTK 4.6 support, use v4_6 feature to unlock the new APIs.
  • Functions that return a glib::Value have now a corresponding function that gets you inner value T directly. Note those will panic if you provide the wrong type.
  • GtkExpression helpers to make their usage nicer from Rust:
    let button = gtk::Button::new();
    button.set_label("Label property");
    
    let label_expression = button.property_expression("label");
    
  • A bunch of fixed issues, documentation improvements and new book chapters!

Other crates §

Other than the classic gtk-rs crates, we have also worked on ensuring the ones in https://gitlab.gnome.org/World/Rust and the broader ecosystem received an update:

Changes §

For the interested ones, 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: