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();
}
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.