Rust+GNOME Hackfest #6

Last week, I went to the sixth Rust+GNOME hackfest which was in Rome. During these hackfests, we work on improving the integration between Rust and the GNOME libraries.

Improvement for builders

In the previous Hackfest, I added the code generation for builders, but there was something that was not convenient. The builder methods had the specific type like in:

pub fn application(mut self, application: &Application) -> Self {
    self.application = Some(application.clone());
    self
}

Here, the type Application could be the parent type of the type of the variable we have (remember, GTK+ is object-oriented), which required explicit upcast from the call as in:

builder.application(&app.upcast());
pub fn application<P: IsA<Application>>(mut self, application: &P) -> Self {
    self.application = Some(application.clone().upcast());
    self
}

So, now we can do:

builder.application(&app);

which is way more convenient.

Compile time improvement to gir

The code generator we use to generate most of the code for GTK+ Rust bindings, gir, was really slow to compile. On my computer, it used to take 4m15 for a clean release build and 1m30 for a rebuild. So, making changes to it was annoying since we had to wait a very long time. I started to make this better by removing dependencies. Now, a clean build takes 1m28 and a rebuild, 55s. There’s still a lot of room for improvement, but this requires significant work: replacing the regex crate and rewriting the toml crate so that it does not use serde.

Clone macro

Finally, I created the clone! macro which can be really useful to pass reference counted data to an event handler.

For instance, currently we have to write something like this:

let state = Rc::new(RefCell::new(State::new()));
let state2 = Rc::new(RefCell::new(State::new()));

{
    // Create a new scope to avoid shadowing the references in the
    // outer scope.
    let state = Rc::downgrade(&state);
    let state2 = Rc::clone(&state2);
    application.connect_activate(move |app| {
        let state =
            match state.upgrade() {
                Some(state) => state,
                None => return,
            };
        // Use state and state2.
    }));
}

Here, you can see that we have to juggle between downgrading to get a weak reference and upgrading, which can fail. All of this is really cumbersome.

And now, we can do this:

let state = Rc::new(RefCell::new(State::new()));
let state2 = Rc::new(RefCell::new(State::new()));

application.connect_activate(clone!(@weak state, state2 => move |app| {
    // Use state and state2.
}));

We use the @weak marker to specify that we want a weak reference and no marker for a strong reference.

We can also specify the return value in case we cannot upgrade as in:

clone!(@weak state => @default-return 42, move |_| {
    state.borrow_mut().started = true;
    10
})

In this case, if the state value was freed, it cannot be upgraded and this will return 42. Otherwise, the code in the closure will be executed and will return 10.

Thanks to this macro, it is now much easier to avoid leaking memory by doing weak references.

Thanks

A big thank to Antonio Piraino for organizing the Hackfest, a big thank to Asset Data for the venue and a huge thank to the GNOME foundation for sponsoring my flight and accommodation to allow me to participate to this Hackfest.

GNOME Foundation