Cursive: closures with static lifetime

Hi there,

I am new to Rust and I am struggling finding a way around the static lifetime constraint set for callbacks in the Cursive library (i.e. add_global_callback, and thereafter button). Looks like another user raised a similar help request recently. I have tried to provide a fully reproducible example, adapted from Cursive's main repo.

From some of the related discussions, I understand I can use smart pointers as a way around the static constraint. I had a go at this using RefCell, unfortunately with no luck...

use cursive::event::{Event, Key};
use cursive::traits::*;
use cursive::views::{Dialog, EditView, OnEventView, TextArea};
use cursive::Cursive;
use std::cell::RefCell;

struct Item {
  name: String
}

fn main() {
    let mut siv = Cursive::default();
    let item = RefCell::new(Item { name: "some name".to_string() });

    siv.add_layer(
        Dialog::new()
            .title("Describe your issue")
            .padding((1, 1, 1, 0))
            .content(TextArea::new().with_id("text"))
            .button("Ok", Cursive::quit),
    );

    siv.add_layer(Dialog::info("Hint: press Ctrl-F to find in text!"));

    siv.add_global_callback(Event::CtrlChar('f'), |s| {
        s.add_layer(
            OnEventView::new(
                Dialog::new()
                    .title("Change item")
                    .content(
                        EditView::new()
                            .with_id("edit")
                            .min_width(10),
                    )
                    .button("Ok", move |s| {
                        let text =
                            s.call_on_id("edit", |view: &mut EditView| {
                                view.get_content()
                            }).unwrap();
                        *item.borrow_mut() = Item {
                           name: text.to_string()
                        }; 
                    })
                    .dismiss_button("Cancel"),
            ).on_event(Event::Key(Key::Esc), |s| {
                s.pop_layer();
            }),
        )
    });

    siv.run();
}

This doesn't compile:

error[E0373]: closure may outlive the current function, but it borrows `item`, which is owned by the current function
  --> examples/text_area.rs:30:51
   |
30 |     siv.add_global_callback(Event::CtrlChar('f'), |s| {
   |                                                   ^^^ may outlive borrowed value `item`
...
47 |                         *item.borrow_mut() = Item {
   |                          ---- `item` is borrowed here
help: to force the closure to take ownership of `item` (and any other referenced variables), use the `move` keyword
   |
30 |     siv.add_global_callback(Event::CtrlChar('f'), move |s| {
   |                                                   ^^^^^^^^

When I try to follow the compiler tip and the move keyword, I endup with another error:

error[E0507]: cannot move out of captured outer variable in an `FnMut` closure
  --> examples/text_area.rs:42:35
   |
15 |     let item = RefCell::new(Item { name: "some name".to_string() });
   |         ---- captured outer variable
...
42 |                     .button("Ok", move |s| {
   |                                   ^^^^^^^^ cannot move out of captured outer variable in an `FnMut` closure

You can use an Rc to ref-count the item, then you move a clone inside the inner closure. Something like this:

let item = Rc::new(RefCell::new(Item {
    name: "some name".to_string(),
}));
...
siv.add_global_callback(Event::CtrlChar('f'), move |s| {
    let item = item.clone();
    ...
}

P.s.: I don't know in the real case, but here you could use a Cell instead of a RefCell, avoiding some runtime overhead and letting the compiler help you in some cases.

Hi @dodomorandi

Just did what you suggested:

  • wrapped the RefCell into an Rc.
  • added the move keyword to the first callback
 siv.add_global_callback(Event::CtrlChar('f'), move |s| {
        s.add_layer(
            OnEventView::new(
                Dialog::new()
                    .title("Find")
                    .content(
                        EditView::new()
                            .with_id("edit")
                            .min_width(10),
                    )
                    .button("Ok", |s| {
                        let _text =
                            s.call_on_id("edit", |view: &mut EditView| {
                                view.get_content()
                            }).unwrap();
                        item.clone().replace(Item {
                          name: "updated name".to_string()
                        });
                    })

Unfortunately compilation still fails:

   Compiling cursive v0.9.1-alpha.0 (file:///Users/andrfior/code/Cursive)
error[E0597]: `*item` does not live long enough
  --> examples/text_area.rs:48:25
   |
43 |                     .button("Ok", |s| {
   |                                   --- capture occurs here
...
48 |                         item.clone().replace(Item {
   |                         ^^^^ borrowed value does not live long enough
   |
   = note: borrowed value must be valid for the static lifetime...
note: ...but borrowed value is only valid for the lifetime  as defined on the body at 31:51
  --> examples/text_area.rs:31:51
   |
31 |     siv.add_global_callback(Event::CtrlChar('f'), move |s| {
   |                                                   ^^^^^^^^

error: aborting due to previous error

This points to the second, nested callback. Should I clone the Rc twice? once for each scope...

Sorry for not being clear enough, the cloning of the Rc is inside the outer closure, and the clone is moved inside the inner, in the following way:

// Here the item is moved inside
siv.add_global_callback(Event::CtrlChar('f'), move |s| {
    // Now the original `item` is shadowed, and `item` refers to a cloned Rc
    let item = item.clone();
    s.add_layer(
        OnEventView::new(
            Dialog::new()
                .title("Change item")
                .content(EditView::new().with_id("edit").min_width(10))
                // Here we are moving the clone
                .button("Ok", move |s| {
                    let text = s.call_on_id("edit", |view: &mut EditView| view.get_content())
                        .unwrap();
                    item.set(Item {
                        name: text.to_string(),
                    });
                })
                .dismiss_button("Cancel"),
        ).on_event(Event::Key(Key::Esc), |s| {
            s.pop_layer();
        }),
    )
});

Got it!

Thanks a lot for your answer and clarification @dodomorandi. this unblocks me!

I wanted to thank @dodomorandi, too. I've been wrestling with a similar problem for a while and this post led me to use Rc with a RefCell and be able to move a mutatable copy of a struct into a closure and then still access that closure later.

I know it sounds like a little thing, but I have fought through several non-solutions. Thanks to the community.

Step-by-step guide to writing closures with the right bounds

  1. When you need 'static, the closure should have a move annotation;

    • When you don't have a 'static bound, such as with iterator adapters or ::crossbeam::thread::scope, no move is required, and instead of reference counting, classic references suffice.

      • When the closure must be Fn, only shared references (& _) can be used, hence excluding mutation unless interior mutability is used (e.g., RefCell, RwLock);

      • Otherwise, (FnMut and FnOnce closures), both shared (& _) and unique (&mut _) references can be used.

  2. If you still need access from outside the move closure to a captured variable, the best solution is reference counting:

    • Rc when in a single-threaded scenario (such as ::cursive's event loop);

    • Arc otherwise (such as with ::std::thread::spawn());

      • This situation can be spotted thanks to a Send bound on the closure;
    • Then, instead of

      move |/* args */| {
          /* uses captured_var */
      }
      

      the closure becomes

      {
         let captured_variable = Arc::clone(&captured_variable); // or Rc::clone
         move |/* args */| {
             /* uses captured_variable */
         }
      }
      
  3. If you need mutation, you then need interior mutability:

7 Likes

To add to what @Yandros said you can read my blog about closures

1 Like