Search code examples
rustcallbacktraits

callback using self


I am trying to use a callback that uses the self parameter of an object. The part that prevents this from compiling is the self.make_sandwhich() bit. How can I do something like this and have it actually compile in rust? The objective is to make ThingUser able to specify functionality for a Thing. In this case, when you junk the potato, the user makes a sandwhich.

pub trait Thing {
  fn stuff(&self);
  fn junk(&mut self);
}

pub struct Potato {
    thing: Box<dyn FnMut()+Send>,
}

impl Potato {
  fn new() -> Self {
    Self{
        thing: Box::new(||{println!("Original callback");}),
    }
  }
  
  fn give_callback(&mut self, thing: Box<dyn FnMut()+Send>) {
      self.thing = thing;
  }
}

impl Thing for Potato {
  fn stuff(&self) {
  }
  fn junk(&mut self) {
    (self.thing)();
  }
}

fn make_thing() -> Box<dyn Thing> {
    Box::new(Potato::new())
}

pub trait ThingUser {
    fn get_thing(&mut self) -> Option<Box<dyn Thing>>;
}

pub struct Person {
    
}

impl Person {
    fn make_sandwhich(&self) {
        println!("I made a sandwhich");
    }
}

impl ThingUser for Person {
    fn get_thing(&mut self) -> Option<Box<dyn Thing>> {
        let mut p = Potato::new();
        p.give_callback(Box::new(||{self.make_sandwhich()}));
        Some(Box::new(p))
    }
}

fn main() {
    println!("Hello, world!");
  let mut p  = Potato::new();
  p.stuff();
  
  p.give_callback(Box::new(||{println!("Callback");}));
  (p.thing)();
  p.junk();
  
  let mut q = make_thing();
  q.junk();
  
  let mut tu = Person{};
  let used_thing = tu.get_thing();
  used_thing.unwrap().junk();
}


Solution

  • The design you posted cannot work. In ThingUser::get_thing it takes &mut self, which means that self is a borrow of type &mut dyn ThingUser. The closure || { self.make_sandwhich() } must also borrow self (although it only needs a non-mut &dyn ThingUser to call make_sandwhich). That borrow of self must live as long as the closure lives.

    However, you want to store the closure inside a Potato. This would mean that after storing it the Potato is also borrowing &ThingUser, but Potato is not defined to borrow anything at all (there are no lifetime parameters in the definition of potato). Therefore any closure you will ever be able to pass to give_callback must have a 'static lifetime (meaning that it doesn't borrow anything, except possibly things that live for the entire length of the program).

    This is what's being expressed by your compiler error (playground)

    error: lifetime may not live long enough
      --> src/main.rs:51:25
       |
    49 |     fn get_thing(&mut self) -> Option<Box<dyn Thing>> {
       |                  - let's call the lifetime of this reference `'1`
    50 |         let mut p = Potato::new();
    51 |         p.give_callback(Box::new(||{self.make_sandwhich()}));
       |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cast requires that `'1` must outlive `'static`
    

    The compiler is stopping you from doing this:

    let person = Person {};
    let mut potato = Potato::new();
    p.give_callback(Box::new(|| { person.make_sandwhich() } ));
    drop(person);
    potato.junk(); // ! This would be trying to call `person.make_sandwhich()`, but `person` has been dropped! This is a horrible memory safety error!
    

    Broadly you have two options.

    First would be to define Potato with a lifetime parameter, but this will get messy quickly as the lifetime parameter will by tied to the potato type and will complicate pretty much every use of Potato ever.

    The second (probably preferable) option is to make your closure have a 'static lifetime. This could be done by storing your person in an Arc<Person>, for example. Then you use a move closure to create a closure which owns an Arc<Person>, so that Person cannot be dropped until the closure is dropped. For example

    let person = Arc::new(Person {});
    let mut potato = Potato::new();
    let person_clone = person.clone();  // Not strictly necessary for this simple example, but I'm assuming you might want access to `person` after creating the callback in which case this is necessary
    p.give_callback(Box::new(move || { person_clone.make_sandwhich() } ));
    drop(person);
    potato.junk(); // This is now fine! The first `Arc<Person>` was dropped, but the closure is still holding one so the internal `Person` object has not been dropped. Hooray!
    

    P.S. I highly recommend you use rustfmt to format your code (ideally on every save). It will make you faster writing it and will help people read it.