Lifetime problems in closure

Hi,
I'm trying to make a GTK app, but i have a lifetime problems when i call glib::Receiver::attach.
How can i solve this??

    fn do_action(&self, action: Action) -> glib::Continue {
        match action {
            Action::Show(monster) => {
                let single = Single::new(monster);
                self.header.change_title(&single.name);
                self.content.stack.set_visible_child(&single.container);
            }
        }
        glib::Continue(true)
    }

    fn connect_events(&self) {
        let receiver = self.receiver.borrow_mut().take().unwrap();
        receiver.attach(None, |action| self.do_action(action));
        self.window.connect_delete_event(move |_, _| {
            gtk::main_quit();
            gio::signal::Inhibit(false)
        });
    }

The problem

receiver.attach API requires a 'static closure, that is, a closure that is statically guaranteed not to carry a dangling pointer/reference at any point.

In your case, however, the closure is capturing self: &'_ Self, i.e., a reference to Self guaranteed to be valid only for some anonymous lifetime '_ that may very well be shorter than 'static. In other words, the self: &'_ Self may very well dangle at some point in the program, which is not acceptable given the API requirements of receiver.attach.

The solution

Any time this kind of issues happens, it comes from the fact that one must avoid borrowing, which means that taking ownership is required.

You thus cannot capture a &'_ Self or a &'_ mut Self, but a Self, Box<Self>, Arc<Self>, etc. : something that owns Self.

So the first reflex is to self.clone(), to effectively get a value of type Self:

let captured: Self = self.clone();
receiver.attach(None, move |action| this.do_action(action));
  • (move is usually required in this kind of cases to tell Rust "to capture owned variables rather than references / borrows"; you can read this nice blog post to understand what a closure "capture" actually is)

Something along these lines should compile.

The only issue with this is that the whole object is "copied" (cloned) when in other languages just copything the pointer would have sufficed.

If that's what you want to do, the way to express that in Rust is by using the Arc wrapper (or Rc as a micro-optimization for a guaranteed single-threaded context): when an Arc is .cloned, it's just a pointer that is copied:

    fn do_action(&self, action: Action) -> glib::Continue {
        match action {
            Action::Show(monster) => {
                let single = Single::new(monster);
                self.header.change_title(&single.name);
                self.content.stack.set_visible_child(&single.container);
            }
        }
        glib::Continue(true)
    }

    fn connect_events(self: Arc<Self>) {
        let receiver = self.receiver.borrow_mut().take().unwrap();
        let self_clone = Arc::clone(&self);
        receiver.attach(None, move |action| self_clone.do_action(action));
        self.window.connect_delete_event(move |_, _| {
            gtk::main_quit();
            gio::signal::Inhibit(false)
        });
    }

or something along these lines (ideally you manage to just Arc-wrap the fields required for the .do_action()).

2 Likes

thanks works, but is this the better way to do??

Yandro's solution is the optimal way. I recommend that you take time to understand why you need an owned version instead of a reference.

@Yandros: My question to you (or anyone) that I would like entertained:

Suppose Closure A contains the original Arc<Self>. Let Closure B be a subset of closure A. Then, I pass into closure B (which suppose is the connect_events above) a cloned version of the original Arc<Self>.

Question: If closure A drops the original Arc, what happens to the other Arc? Or, is this not possible? It seems that since there is a static requirement for the function input within closure B, closure A's stack frame may very well get dropped (thus dropping the original Arc, meaning... the cloned Arc dangles? I sense that there is some Arc magic going on). Does the answer have something to do with weak and strong references?

An Arc's content won't be destroyed until the last one is dropped. That's the whole point of a reference counted container.
Its content doesn't live on the stack as you can see here.

Weak is another subject, to do anything with it you have to upgrade it. If the Arc's content doesn't exist anymore, upgrade will return None. And Weak won't keep the content alive.

2 Likes

You summarized it perfectly. Thank you!