Ilia
Ilia

Reputation: 570

How to move data into multiple Rust closures?

I have a two widgets in a simple GTK app:

extern crate gdk;
extern crate gtk;

use super::desktop_entry::DesktopEntry;

use gdk::enums::key;
use gtk::prelude::*;

pub fn launch_ui(_desktop_entries: Vec<DesktopEntry>) {
    gtk::init().unwrap();

    let builder = gtk::Builder::new_from_string(include_str!("interface.glade"));

    let window: gtk::Window = builder.get_object("main_window").unwrap();
    let search_entry: gtk::SearchEntry = builder.get_object("search_entry").unwrap();
    let list_box: gtk::ListBox = builder.get_object("list_box").unwrap();

    window.show_all();

    search_entry.connect_search_changed(move |_se| {
        let _a = list_box.get_selected_rows();
    });

    window.connect_key_press_event(move |_, key| {
        match key.get_keyval() {
            key::Down => {
                list_box.unselect_all();
            }
            _ => {}
        }
        gtk::Inhibit(false)
    });

    gtk::main();
}

I need to change list_box from both events. I have two closures that move, but it is not possible to move list_box to both closures simultaneously as I get the error:

error[E0382]: capture of moved value: `list_box`

What can I do?

Upvotes: 7

Views: 2493

Answers (3)

Sven Marnach
Sven Marnach

Reputation: 601599

As explained in Shepmaster's answer, you can only move a value out of a variable once, and the compiler will prevent you from doing it a second time. I'll try to add a bit of specific context for this use case. Most of this is from my memory of having used GTK from C ages ago, and a few bits I just looked up in the gtk-rs documentation, so I'm sure I got some details wrong, but I think the general gist is accurate.

Let's first take a look at why you need to move the value into the closures in the first place. The methods you call on list_box inside both closures take self by reference, so you don't actually consume the list box in the closures. This means it would be perfectly valid to define the two closures without the move specifiers – you only need read-only references to list_box, you are allowed to have more than one read-only reference at once, and list_box lives at least as long as the closures.

However, while you are allowed to define the two closures without moving list_box into them, you can't pass the closures defined this way to gtk-rs: all functions connecting event handlers only accept "static" functions, e.g.

fn connect_search_changed<F: Fn(&Self) + 'static>(
    &self, 
    f: F
) -> SignalHandlerId

The type F of the handler has the trait bound Fn(&Self) + 'static, which means that the closure either can't hold any references at all, or all references it holds must have static lifetime. If we don't move list_box into the closure, the closure will hold a non-static reference to it. So we need to get rid of the reference before being able to use the function as an event handler.

Why does gtk-rs impose this limitation? The reason is that gtk-rs is a wrapper around a set of C libraries, and a pointer to the callback is eventually passed on to the underlying glib library. Since C does not have any concept of lifetimes, the only way to do this safely is to require that there aren't any references that may become invalid.

We have now established that our closures can't hold any references. We still need to access list_box from the closures, so what are our options? If you only have a single closure, using move does the trick – by moving list_box into the closure, the closure becomes its owner. However, we have seen that this doesn't work for more than one closure, because we can only move list_box once. We need to find a way to have multiple owners for it, and the Rust standard library provides such a way: the reference-counting pointers Rc and Arc. The former is used for values that are only accessed from the current thread, while the latter is safe to move to other threads.

If I remember correctly, glib executes all event handlers in the main thread, and the trait bounds for the closure reflect this: the closure isn't required to be Send or Sync, so we should be able to make do with Rc. Morevoer, we only need read access to list_box in the closures, so we don't need RefCell or Mutex for interior mutability in this case. In summary, all you need is probably this:

use std::rc::Rc;
let list_box: gtk::ListBox = builder.get_object("list_box").unwrap();
let list_box_1 = Rc::new(list_box);
let list_box_2 = list_box_1.clone();

Now you have two "owned" pointers to the same list box, and these pointers can be moved into the two closures.

Disclaimer: I couldn't really test any of this, since your example code isn't self-contained.

Upvotes: 9

You can use cloning on the gtk-rs widgets.

In gtk-rs every object implementing gtk::Widget (so basically every GTK object you can use inside a gtk::Window) must also implement the Clone trait. Calling clone() is very cheap because it's just a pointer copy and a reference counter update.

Knowing this below is valid and cheap:

let list_box_clone = list_box.clone();
search_entry.connect_search_changed(move |_se| {
    let _a = list_box.get_selected_rows();
});

But since this solution is verbose and gets very ugly very soon if you have more than one objects to move, the community came up with the following macro:

macro_rules! clone {
    (@param _) => ( _ );
    (@param $x:ident) => ( $x );
    ($($n:ident),+ => move || $body:expr) => (
        {
            $( let $n = $n.clone(); )+
            move || $body
        }
    );
    ($($n:ident),+ => move |$($p:tt),+| $body:expr) => (
        {
            $( let $n = $n.clone(); )+
            move |$(clone!(@param $p),)+| $body
        }
    );
}

The usage is very simple:

search_entry.connect_search_changed(clone!(list_box => move |_se| {
    let _a = list_box.get_selected_rows();
}));

This macro is capable of cloning any number of objects that are moved into a closure.

For further explanation and examples check out this tutorial from the gtk-rs team: Callbacks and closures

Upvotes: 8

Shepmaster
Shepmaster

Reputation: 430673

You literally cannot do this. I encourage you to go back and re-read The Rust Programming Language to refresh yourself on ownership. When a non-Copy type is moved, it's gone — this is a giant reason that Rust even exists: to track this so the programmer doesn't have to.

If a type is Copy, the compiler will automatically make the copy for you. If a type is Clone, then you must invoke the clone explicitly.

You will need to change to shared ownership and most likely interior mutability.

Shared ownership allows a single piece of data to be jointly owned by multiple values, creating additional owners via cloning.

Interior mutability is needed because Rust disallows multiple mutable references to one item at the same time.

Wrap your list_box in a Mutex and then an Arc (Arc<Mutex<T>>). Clone the Arc for each handler and move that clone into the handler. You can then lock the list_box and make whatever changes you need.

See also:

Upvotes: 3

Related Questions