Mutating the same data in multiple 'static clo

2019-02-25 04:33发布

问题:

Given a library (for instance a GUI library) that uses callbacks to communicate events to the library user, how would I proceed to have proper mutability in the program? For instance:

// so I have this `obj` I want to modify when the user clicks buttons
// in the GUI

let same_action = move |_| {
  // modify obj in several ways, e.g. obj.text = "Modified"
  obj.text = "Modified";
}
// --> I had to move `obj` above since `on_click` has a `'static`
// constraint for `F` but that's not really a thing I want to do,
// I want to keep control of `obj` in the outer scope!!!

// --> error because `same_action` does not implement `Fn` since it
// mutates the moved `obj` in it
button1.on_click(same_action);
// --> if the above worked, here we'd have a error because `button1`
// has moved `same_action`
button2.on_click(same_action);
// --> assuming all of the above worked, we'd have a error here about
// unable to use `obj` because it has been moved to same_action
button3.on_click(move |_| obj.text = "Another modifier");

// the library now process the gui and call the callbacks in a loop
// until exit
gui_run();

// --> ..., error cannot use `obj` because it has been moved by
// `same_action`
println!("Final value: {}", obj.text);

See the comments with // --> for the critical points of this question.

This seems like a pretty common problem to worry on event-driven APIs in Rust. How'd one get around it?

回答1:

If you need to share mutable data, you need some container that make sure that the aliasing rules are being followed, most likely one from std::cell. For Copy data this is Cell, for other types there's RefCell. Then the closures can use either:

  • Cell<TheObject>/Cell<TheObject>, or
  • TheObject where some fields are Cells and RefCells.

Which is better depends on how granular you want the mutability to be and whether this is a common need for all users of TheObject or just for this particular closure. In the second case, you need to alter the definition of TheObject, in the first you'd do something like this:

let obj = RefCell::new(obj);

let same_action = move |_| {
  obj.borrow_mut().text = "Modified";
}

If you furthermore can't have borrows in the closure's captured values, for example because of a 'static bound, you can put the RefCell into an Rc.

Also, you can't pass same_action to two buttons because closures can't be copied or cloned. In general this can't be done, because they might close over things that can't be copied or cloned. When possible, it may be allowed in the future, for now you can work around it with a macro, a function (that would have to box the closure), or by simply writing the closure twice if it's simple.



标签: rust