Core problem is that, h
uses self.name
and f
inside it's implementation.
Rust closures by default capture by reference (borrow), so if you store your callback inside MyStruct
, captured f
will not live enough, because it will be destroyed (dropped), after execution leaves set_handler
block.
Another problem is that by default values stored in Box<_>
should live as long as 'static
.
Compiler automaticaly tries to assign apropriate lifetime for &mut self
, it defaults to assume, that self
should live as long as set_handler
function executes.
You're essentially trying to create self-referential struct. MyStruct
references itself inside callback. Simplest solution will be to just clone
name and remove self-reference.
Here we force closure to take ownership of variables used inside it and clone self.name
, so closure doesn't borrow self
let name = self.name.clone();
let h = move |s: String| {
f(format!("{} {}", name, s));
};
More complicated solution will be, to tell compiler that MyStruct
can't move, because self-refential types can be safe only when they don't move around changing their reference (borrow) address and tell compiler that data in closure should live as long as MyStruct
. This is more complicated.
Refactored code using Pin<_>
and some carefully written unsafe
code.
use std::marker::PhantomPinned;
use std::pin::Pin;
use std::ptr::NonNull;
// Alias supertrait
trait Callback: Fn(String) -> () {}
impl<T> Callback for T where T: Fn(String) -> () {}
struct MyStruct {
name: String,
handler: Box<dyn Callback>,
_pin: PhantomPinned,
}
impl MyStruct {
pub fn new() -> Pin<Box<MyStruct>> {
Box::pin(MyStruct {
name: String::from("hello"),
handler: Box::new(|_| { Default::default() }),
_pin: PhantomPinned,
})
}
// Extracting callback is essentially safe, because if you save it, it can't live longer than pinned MyStruct. Lifetimes are elided.
pub fn get_handler_mut(self: Pin<&mut MyStruct>) -> &mut Box<dyn Callback> {
unsafe {
&mut self.get_unchecked_mut().handler
}
}
pub fn set_handler(self: Pin<&mut MyStruct>, f: impl Callback + 'static) {
// Create non null, raw pointer. Type is pinned and lifetimes are set by compiler for get_handler_mut(), everything is safe.
let name = NonNull::from(&self.name);
let wrapper = move |s: String| {
// This is safe, because self is pinned, so name can't point to dangling pointer.
let name = unsafe { name.as_ref() };
f(format!("{} {}", name, s));
};
unsafe {
// We know that assigning to `handler` will not move self, so it's safe.
self.get_unchecked_mut().handler = Box::new(wrapper);
}
}
}
fn main() {
let mut my_struct = MyStruct::new();
my_struct.as_mut().set_handler(|s: String| {
println!("{}", s)
});
let handler = my_struct.as_mut().get_handler_mut();
(handler)("test".to_owned())
}
Another safe, but less effective solution will be to store self.name
in reference counted smart pointer. Rc
will manage lifetime of name
automatically.
use std::rc::Rc;
// Alias supertrait
trait Callback: Fn(String) -> () {}
impl<T> Callback for T where T: Fn(String) -> () {}
struct MyStruct {
name: Rc<String>,
handler: Box<dyn Callback>,
}
impl MyStruct {
pub fn new() -> MyStruct {
MyStruct {
name: Rc::new("hello".to_owned()),
handler: Box::new(|_| { Default::default() }),
}
}
pub fn get_handler(&self) -> &Box<dyn Callback> {
&self.handler
}
pub fn set_handler(&mut self, f: impl Callback + 'static) {
let name = self.name.clone();
let wrapper = move |s: String| {
f(format!("{} {}", name, s));
};
self.handler = Box::new(wrapper);
}
}
fn main() {
let mut my_struct = MyStruct::new();
my_struct.set_handler(|s: String| {
println!("{}", s)
});
let handler = my_struct.get_handler();
(handler)("test".to_owned())
}
Conclusion. Self-referential structs in Rust can be quite complicated, because language tries to deal with unsafety and relies on RAII and manual memory management. There is no GC taking care of things. In production for this example I would use Rc
or cloning options (depending on circumstances), because it's the safest approach of all.