Gtk-rs: how to have mutable object in a closure?



In the previous post The Ulam spiral in Rust I described application that generates prime numbers spiral. I have chosen gtk-rs to write the user interface, because of the pretty good documentation and examples in the repository. If something is not documented there, you can easily check the original Gtk library written in C, which was a base for the Rust version. Also, I just like Gtk, no big story.

However some parts became more tricky than expected and this was a good opportunity to learn some of the Rust's memory management concepts.

Mutate the object inside the closure

What I wanted, was to start the Gtk application with already generated image of the prime numbers spiral (contained in gtk::Image widget) and then be able to re-generate the image when user changed something. It could be "Generate" click action for instance to show image in different resolution or color. In gtk-rs you bind action to the button using a closure.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
let generate_button = Button::new_with_label("Generate spiral");

generate_button.connect_clicked(clone!(
        box_vert, image_gtk, spiral => move |_| {

    let window = match window_weak.upgrade() {
        Some(window) => window,
        None => return
    };

    // Remove existing image from layout box
    box_vert.remove(&image_gtk);

    // Generate new image
    let image_gtk: gtk::Image = spiral.generate_to_gtk();

    // Put new image to the layout box
    box_vert.pack_start(&image_gtk, false, false, 20);
    window.show_all();
}));

I wanted to mutate the contents of the gtk::Box box_vert (basically a layout container) every time button is clicked.

The problem with the button closure was that when I added the Gtk image to the box_vert container, the next time the button was pressed, the code was supposed to remove existing image and add new one, but it didn't.

The .remove(&widget) method, that comes from ContainerExt trait, takes a reference to a widget as an argument. But the widget needs to belong to the self in the first place, so you cannot remove a widget that was not added to the container before. I didn't find any method to empty the container completely, so I needed to rely on the remove.

Unfortunately the image was generated inside the closure, so the image_gtk variable was not leaving the scope of the closure, i.e. closure was owning the image. As a consequence, the box_vert.remove(&image_gtk) didn't remove the right reference and well, I got more images...

Ulam spiral Gtk fail

Shared ownership

The solution I came up with was to "hide" the Gtk image in different data structure - an Option, that represents possibility of a value. This structure needed to be a shared reference, because I needed to access the value of it inside and outside the closure. Rc was perfect for that purpose. As usual, when you read Rust documentation, it's striking how concise and useful is the description:

Single-threaded reference-counting pointers. 'Rc' stands for 'Reference Counted'. The type Rc provides shared ownership of a value of type T, allocated in the heap. Invoking clone on Rc produces a new pointer to the same value in the heap. Source: Rust Rc

This is exactly what I needed - a concept called shared ownership, that will allow one object to be owned by more than one owner, i.e. function, struct, closure, etc. Shared ownership is solved by reference counting in Rust.

Reference counting is a garbage collecting technique that is optional in Rust, but in other programming languages, such as Python, does the tough job of cleaning up the memory. Rust manages reference counting in such way, that when you allocate memory for something, let's say - vector with image, the image is placed in the heap with a counter. When you clone the image, you don't copy by value as usual, but instead get pointer to the original place in the heap and increment the counter. When nothing references the image, the reference counter is reduced to zero and the memory is eventually freed. How convenient! *there is a potential danger here

The cloning part in my application was done by clone! macro that I used on arguments passed to the closure. (I took liberty of stealing the macro from gtk-rs examples repo) Macro's job was to just copy values of everything passed to the closure.

One thing was missing - the Reference Counted Option was not mutable and I wanted to change the image inside the closure.

Interior mutability

The RefCell helped to allow mutability inside Rc. In fact the Rc<RefCell<T>> is pretty common structure in Rust, although at some minimal performance cost, because the ownership check is done runtime instead of compile time. It's like the borrow checker is tricked to think that the ownership constraints of the Option are fulfilled, while the real check will be done during the program run. Rust is able to do this safely and this concept is known as interior mutability.

In fact, it's worth mentioning that all gtk-rs objects related to the gtk::Widget struct are implemented in similar way to the Rc<RefCell<T>>. As a consequence cloning of all boxes, buttons and windows is cheap and efficient. The only problematic parts are non-widget Rust objects, for which we need to do some extra work.

The final structure was working, yet it looked somehow more complicated.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// Instantiate Option which will help us mutate the image
// from within the closure.
let image_wrapper: Rc<RefCell<Option<gtk::Image>>> =
    Rc::new(RefCell::new(None));

// (...)

// Bind action to the generate button
generate_button.connect_clicked(clone!(
        box_vert, image_wrapper, spiral, adj_x, radio_primes => move |_| {

    let window = match window_weak.upgrade() {
        Some(window) => window,
        None => return
    };

    // Remove existing image from the Option container
    let mut internal_image = image_wrapper.borrow_mut();
    let image = internal_image.as_ref().unwrap();
    box_vert.remove(image);

    // Create new image
    let image_gtk: gtk::Image = spiral.borrow().generate_to_gtk();

    let img_clone = image_gtk.clone();

    // Replace the reference to the newly generated image in the wrapper
    *internal_image = Some(image_gtk);

    // Put the reference of the new image to the layout container
    box_vert.pack_start(&img_clone, false, false, 20);

    window.show_all();
}));

Add some abstractions

Although not strictly required, it would be nice to hide the implementation internals of the image_wrapper, so for example the definition of Rc<RefCell<Option<gtk::Image>>> which is pretty long. The same applies to all borrow() and borrow_mut() calls, because they don't bring any value to the application logic. Quite the opposite, they make reading the code more difficult. By the way it's always good to avoid unwrap() in real applications and have proper error handling, so the app doesn't crash if somehow image is missing in the container.

To achieve both goals, I created custom type ImageRef, which is the same Option wrapped inside Rc and RefCell. The new custom type is intended to be used through a struct ImageWrapper, holding a field for ImageRef. Thanks to the struct, we can move borrow logic and error handling to two methods - get_image and set_image.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
type ImageRef = Rc<RefCell<Option<gtk::Image>>>;

#[derive(Clone)]
struct ImageWrapper {
    internal_value: ImageRef
}

impl ImageWrapper {
    fn new() -> ImageWrapper {
        ImageWrapper { internal_value: Rc::new(RefCell::new(None)) }
    }

    fn get_image(&self) -> Result<gtk::Image, &str> {
        let image_wrapper = self.internal_value.borrow();
        match image_wrapper.as_ref() {
            Some(image) => Ok(image.clone()),
            None => Err("Expected an image!")
        }
    }

    fn set_image(&self, image_gtk: gtk::Image) {
        let mut image = self.internal_value.borrow_mut();
        *image = Some(image_gtk);
    }
}

The new interface can be used as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
let image_wrapper = ImageWrapper::new();

// Add newly generated image.
image_wrapper.set_image(image_gtk);

// Remove existing image from the wrapper struct.
match image_wrapper.get_image() {
    Ok(image) => box_vert.remove(&image),
    Err(e) => println!("{}", e),
}

That's it! Gtk images are replaced as expected!

Gtk app result

Summary

In Gtk Rust library, Rc and RefCell constructs are pretty useful and common because of the implementation decisions in the library itself. It's convenient to operate on native Gtk widget types, however sometimes a specific use case demands shared access and mutability in places, that would be normally forbidden by the borrow checker. Rc and RefCell help to reintroduce mutability without the risk of the race condition and they do it cheap - you still can benefit from incredible performance of Rust.

Review update 2019-02-23

Thanks to sdroege's suggestions I was able to improve code a lot. In the previous version I used a HashMap to store the image, instead of Option, which is more suitable for having possibility of only one object.

Danger!

However it's worth mentioning that there is a hidden danger in cloning some of the reference counted objects (as pointed by sdroege). Imagine following situation:

  1. You want reference to the window inside a button closure, so you pass clonned window there.
  2. The button needs to be displayed in the window, so you attach the button to the window
  3. Rc of the window is incremented by the button and Rc of the button is incremented by the window.
  4. Both objects reference each other and will never release the memory. It's a memory leak.

To avoid this problem, a Weak reference can be used: Weak similarly as I used it on my window instance with upgrade() and downgrade().

Source code:

Ulam spiral on Github

Useful articles:

Gtk-rs closures

Gtk examples from Rustfest Rome

https://ricardomartins.cc/2016/06/08/interior-mutability