[Solved] Gtk-rs, ownership and callbacks


#1

Hello,

so as a kind of weekend project to see how Rust would handle GUI coding, given the recent thread on GUI support, I have decided to port an very old Gnomemm application I have done in the past into modern shinny Rust and Gtk-rs.

The Gtk-rs tutorials have everything inside main, which while great for learning, aren’t that nice for doing actuall applications.

So after having my application fully running inside main, I had the idea of using a struct with some methods instead. Here is when my ownership issues started.

I have solved all of the up to one regarding callbacks, which no matter how I search the Internet, or workarounds that throw at the compiler, nothing seems to make it happy.

Basically I have such a structure

impl GWCApp {
   fn on_menu_open(&self) {
       //....
   }

   fn init_menus (&self) -> MenuBar {
    //...
        file_item.connect_activate( |_| {
            self.on_menu_open();
        });

   }
}

The compiler isn’t happy with my self.on_menu_open() call, although from my point of view, it being a method on self will clearly have the same lifetime as the application.

The full code is at https://github.com/pjmlp/gwc-rs/blob/master/src/main.rs.

So I am kind of lost out to sort this out, even with my modern C++ knowledge, I don’t know where more to dig in what concerns Rust lifetimes.

Can someone just let me know what I am kind of missing?

Thanks


#2

I’m guessing the issue is that the compiler can’t be sure that GWCApp outlives the MenuBar that’s returned and contains a reference to the app.

One option might be to put the fields of GWCApp inside a Rc<RefCell<...>> so that you avoid lifetimes altogether in this scenario.


#3

The docs for connect_activate state that the callback must have a static lifetime. Maybe you’ll need to stash your GWCApp in a lazy_static initializer.


#4

Thanks for the replies.

After going at it for a few more hours, I have decided to throw away the struct, keep all the code in main and sprinkle a few Rc<RefCell<<>>>() as function parameters, while trying to delve into the Gtk-rs examples.


#5

Note that all Gtk objects are already reference counted, so when it’s just about them, one can just use .clone() and let the closure capture the new handle by value.


#6

No, it was a method being called from a closure not allowing to keep a reference to self, as you can see on the example, init_menus() and on_menu_open() are methods on the same structure.

Something that in C++ I would have easily sorted out with enable_shared_from_this or using closure capture list.


#7

As @G2P pointed out, that API requires that the closure you pass holds no references (or 'static references). As mentioned, you should be able to keep your GWCApp (rather than a giant main) but use Rc<RefCell<...>> for its fields. Then you’d pass (move, to be precise) a clone of the Rc to that closure, and that should work.


#8

Thanks once more for the help, but it is too much work for what was a simple weekend project to test waters regarding the state of affairs of doing GUI development with Rust.

I really don’t understand how you would do Rc<RefCell<<…>>> for a method reference, not fields.

So the code is reverted back and it is working as before my GWCApp attempt.


#9

You wouldn’t use a self reference in the function; that reference appears to only be needed to get access to the fields. If you pass the fields directly (via the Rc<...>), then you don’t need &selfand therefore sidestep the lifetimes.


#10

Still want I want is to call a method from the callback, regardless of what that method might eventually be doing.

If for sidestepping the lifetimes I need to pass the fields around, then there is no value in having a struct with methods, instead of a few global functions.

Hence why I reverted everything back to a state I knew things were working properly

Thanks again for the support, maybe I just need to spend more time doing lifetime related exercises before delving into these kind of ideas, because right now I still don’t get it why it doesn’t work for methods.


#11

Usually you need to wrap your stuff. Like so:

struct GWCAppWrapper {
  app: Arc<Mutex<GWCAPP>> // mutex if multithreading
}
impl GWCAppWrapper {
   fn init_menus (&self) -> MenuBar {
    //...
        let app = self.app.clone();
        file_item.connect_activate( move |_| {
            app.lock().unwrap().on_menu_open();
        });
   }
}

#12

Thanks for the tip, I will have another look at it.


#13

Just to update, I decided to have another go at it, and managed to somehow get the data into my GWApp struct.

As solution I made the member data into Rc ones and used static members for the callbacks.

For anyone that might face a similar problem, just check the github repository.

Thanks again for the support.