How can I design my GUI program in such a way that it works well with Rust's safety rules?

I am new to Rust. As such, for practice, I am attempting to write a Notepad clone using the gtk crate. So far, it has been going reasonably well, but I've encountered an issue. In my main function, I create a gtk::TextBuffer object and have a mutable reference to it in a bunch of closures that are passed to the gtk crate to be called when the user interacts in various ways, but rustc complains that my closures can outlive the buffer, which seems questionable since AFAIK the program exits as soon as main returns.

Code sample:

fn main() {
    // ...
    let text_buffer  = TextBuffer::new(None::<&TextTagTable>);
    // ...
    file_new.connect_activate(|_| {
        text_area.get_buffer().unwrap().set_text(""); //for some reason this is allowed even though text_buffer isn't mut, but that's a question for a different thread
    });
    // ...
}

Rustc's Complaint:

error[E0373]: closure may outlive the current function, but it borrows `text_area`, which is owned by the current function
  --> src/main.rs:32:35
   |
32 |         file_new.connect_activate(|_| {
   |                                   ^^^ may outlive borrowed value `text_area`
33 |             text_area.get_buffer().unwrap().set_text("");
   |             --------- `text_area` is borrowed here
   |
note: function requires argument type to outlive `'static`
  --> src/main.rs:32:9
   |
32 | /         file_new.connect_activate(|_| {
33 | |             text_area.get_buffer().unwrap().set_text("");
34 | |         });
   | |__________^
help: to force the closure to take ownership of `text_area` (and any other referenced variables), use the `move` keyword
   |
32 |         file_new.connect_activate(move |_| {
   |                                   ^^^^^^^^

Rustc's suggestion to have the closure take ownership of text_area isn't a good idea because I need many such closures. Perhaps I should have something resembling a class with a member function instead of each lambda, where this class owns the buffer? How can I fix this issue, in an idiomatic way?

In GUI with calllbacks you'll probably want to wrap all shared objects in Rc<RefCell<T>> or Arc<Mutex<T>> (they're the same thing but the second is thread-safe), because the borrow checker can't control when the events will happen, so it won't let you just have a temporary borrow across handlers.

See https://gtk-rs.org/docs-src/tutorial/closures

@kornel: while what you say is generally true, gtk already does the required runtime memory management under the hood, and adding another layer of that doesn't really do anything useful.

Looking at the example you linked, you need to clone the text area for each closure you create that captures it (the clone just copies a pointer, just like the clone forRc or Arc).

Think of gtk-rs UI objects to be internally of the types that @kornel suggested -- pointers wrapping objects with interior mutability. That's why you can mutate through a non-mut pointer.

It doesn't matter what gtk does internally. You're working here with the borrow checker, not gtk. You have to use types that the borrow checker can verify.

Well my point is that the following should work per the docs:

fn main() {
    // ...
    let text_buffer  = TextBuffer::new(None::<&TextTagTable>);
    let text_buffer_cloned = text_buffer.clone():
    // ...
    file_new.connect_activate(move |_| {
        text_buffer_cloned.get_buffer()
            .unwrap().set_text("");
    });
    // ...
}

That clone should just be a pointer copy with a reference count increment.

I think you're misunderstanding something. Have a look at the linked examples from gtk-rs' docs. There is no need for adding Rc<RefCell<_>> or Arc<Mutex<_>> because gtk-rs already provides interior mutability for all its widget types; it would simply make the code longer and harder to read.

1 Like

Is there some way to handle this with less messy syntax? Specifically, it isn't great to me that I need to have a specifically named cloned variable for each closure, since I have a lot of closures. Is there, perhaps, some way that I can implicitly have the closure copy the object?

It can't be done implicitly, but

There's a code example for a macro that makes this kind of thing less noisy on that page.

The glib crate (part of gtk-rs) also includes a variation of this macro.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.