Moving context into several closures?

Viewed 126

I have found a way to move context into several closures, but it looks ugly. I do it with help of Rc and cloning each variable I need to use for each closure. Particularly I don't like to clone every variable for every closure I want to use:

  let mut context = Rc::new( Context { a : 13 } );
  ..

  let context_clone_1 = Rc::clone( &context );
  engine.on_event1( Box::new( move ||
  { 
    println!( "on_event1 : {}", context_clone_1.a );
    ...

  let context_clone_2 = Rc::clone( &context );
  engine.on_event2( Box::new( move ||
  {
    println!( "on_event1 : {}", context_clone_1.a );
    ...

It is an extensive way to go and I feel there must be a better way to do it. Also, uncommenting line // context_clone_1.a += 1; breaks the compilation. What is the proper way of solving problems like this in Rust?

Here is a playground with minimal code.

1 Answers

There are two "problems" here:

Since you specifically asked about context_clone_1.a += 1;: When putting a value into an Rc, there could be multiple references to that value, derived from the independent Rc owners. If mutation was allowed, this would also allow simultaneous mutation and aliasing, which is not allowed in Rust; therefore Rc does not allow mutating its inner value. A common approach to regain mutability is to put the value into a RefCell, which provides mutability through try_borrow_mut() with a runtime check that ensures no aliasing occurs. A Rc<RefCell<T>> is commonly seen in Rust.

Regarding the use of Rc: The way your code is currently set up is actually fine, at least if that's how it should work. The way the code is currently structured allows for flexibility, including cases where multiple Context-objects provide callback implementations on different events. For example, this is currently possible:

let context1 = Context { a : 13 };
engine.on_event1(Box::new(move ||
{ 
  println!("on_event1 : {}", context1.a );
});

let context2 = Context { a : 999 };
engine.on_event2(Box::new(move ||
{
  println!("on_event1 : {}", context2.a );
});

In case you have exactly one Context (as in your example), and since the Engine needs to make sure that all callbacks are alive while it itself is alive, you'll need to put each callback - which is structured as a completely separate thing - into a Rc. In your case, all Rc end up pointing to the same object; but they don't have to and this is what your code currently allows for.

A more simple solution would be to define a trait for Context, something along the lines of

trait EventDriver {
    fn event1(&mut self, &Engine);
    fn event2(&mut self, &Engine);
}

... and then have Context implement the trait. The Engine-struct then becomes generic over E: EventDriver and Context becomes the E in that. This solution only allows for exactly one instance of Context to provide event callbacks. But since Engine is the owner of that object, it can be sure that all callbacks are alive while it itself is alive and the whole Rc-thing goes away.

Related