Problem with using a callback to mutate a captured variable using the cursive crate

Hiya, I'm using the cursive crate to try to make the UI for a game. I have a GameState struct which I want to mutate using callbacks attached to cursive's buttons. My understanding is that because the callback only takes the UI as an input, the callback must capture the gamestate, but the compiler doesn't allow this because it causes an ownership issue?

The error mentioned closures specifically, so I tried factoring out the closure into a proper function but that didn't help either. When I move the callback to be a named variable, the compiler says expected a closure that implements the Fn trait, but this closure only implements FnMut, so it looks to me that I can't mutate the gamestate from that callback.

If I'm going about this the wrong way then please let me know, thank you.

Here's a MWE to show the problem I'm having:

use cursive::views::Button;
use cursive::views::Dialog;
use cursive::views::TextView;
use cursive::Cursive;

pub struct GameState {
    pub day: usize,
}

fn main() {
    // Set up the game
    let mut g = GameState { day: 1 };
    let mut ui = cursive::default();

    // Create the UI
    let button_next_day = Button::new("Next day", move |s: &mut Cursive| {
        // Mutate game state
        g.day += 1;
        // Mutate the UI to show a dialog
        let popup_text = format!("It is now day {}.", g.day);
        let popup_layer = Dialog::around(TextView::new(popup_text)).dismiss_button("Ok");
        s.add_layer(popup_layer);
    });
    let main_layer = Dialog::around(button_next_day);
    ui.add_layer(main_layer);
    ui.run();
}

This produces the following errors:

Errors produced
   Compiling test-package v0.1.0 (/home/hannes/Documents/rust/test)
error[E0594]: cannot assign to `g.day`, as `Fn` closures cannot mutate their captured variables
  --> src/main.rs:18:9
   |
18 |         g.day += 1;
   |         ^^^^^^^^^^ cannot assign

error[E0373]: closure may outlive the current function, but it borrows `g`, which is owned by the current function
  --> src/main.rs:16:52
   |
16 |     let button_next_day = Button::new("Next day",  |s: &mut Cursive| {
   |                                                    ^^^^^^^^^^^^^^^^^ may outlive borrowed value `g`
17 |         // Mutate game state
18 |         g.day += 1;
   |         - `g` is borrowed here
   |
note: function requires argument type to outlive `'static`
  --> src/main.rs:16:27
   |
16 |       let button_next_day = Button::new("Next day",  |s: &mut Cursive| {
   |  ___________________________^
17 | |         // Mutate game state
18 | |         g.day += 1;
19 | |         // Mutate the UI to show a dialog
...  |
22 | |         s.add_layer(popup_layer);
23 | |     });
   | |______^
help: to force the closure to take ownership of `g` (and any other referenced variables), use the `move` keyword
   |
16 |     let button_next_day = Button::new("Next day",  move |s: &mut Cursive| {
   |                                                    ^^^^^^^^^^^^^^^^^^^^^^

You probably don't want to capture the GameState like this, because then it is only visible from within a single closure. (And given that these are Fn closures, it's also safe to assume they could be called from multiple threads.) To fix this, you'd typically put your GameState inside some kind of Arc/Rc smart pointer to give it shared ownership, and capture the pointer in the closure instead of the GameState itself. And if you want to mutate it within the closure, you'll really want some sort of locking mechanism (RefCell/Mutex/RwLock) so that you can ensure unique access when you're writing to it.

You can read about this on the doc page for interior mutability.

In fact, the structure of your code is almost exactly the same as the example in the Mutex docs.

Thank you, I felt like there was a better way but had no idea where to start looking!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.