I want to store a callback that can take different types of parameters (both owned values and references), and can also modify its environment (hence the FnMut). When invoking the callback with a reference, I'd like the compiler to enforce that the parameter is only valid in the closure body. I've tried to implement this using boxed closures.
A minimum example shown below:
fn main() {
let mut caller = Caller::new();
let callback = |x: &Foo| println!("{:?}", x);
caller.register(callback);
let foo = Foo{
bar: 1,
baz: 2,
};
//callback(&foo); // works
caller.invoke(&foo); // borrowed value does not live long enough
}
struct Caller<'a, T> {
callback: Box<dyn FnMut(T) + 'a>
}
impl<'a, T> Caller<'a, T> {
fn new() -> Self {
Caller {
callback: Box::new(|_| ()),
}
}
fn register(&mut self, cb: impl FnMut(T) + 'a) {
self.callback = Box::new(cb);
}
fn invoke(&mut self, x: T) {
(self.callback)(x);
}
}
#[derive(Debug, Clone)]
struct Foo {
bar: i32,
baz: i32,
}
I want to understand why this works if I directly call callback()
but the compiler complains about lifetimes if I invoke it through a struct than owns the closure. Perhaps it has something to do with the Box
? I can get this to work if I define foo
before caller
, but I'd like to avoid this.
This is yet another example of the compiler's type inference quirks when working with closures and bounds of a similar sort ( issue #41078 ). Although this Caller<'a, T>
may seem to be well capable of handling invoke
calls for a given generic T
, the given example passes a reference &'b Foo
(where 'b
would be some anonymous lifetime of that value). And due to this limitation, T
was inferred to be a &Foo
of one expected lifetime, which is different from a reference of any lifetime to a value of type Foo
( for<'a> &'a Foo
), and incompatible with the reference passed to the invoke
call.
By not passing the closure to Caller
, the compiler would be able to correctly infer the expected parameter type of the callback, including reference lifetime.
One way to overcome this is to redefine Caller
to explicitly receive a reference value as the callback parameter. This changes the behavior of the inferred type &T
into a higher-ranked lifetime bound, as hinted above.
fn main() {
let mut caller = Caller::new();
let callback = |x: &Foo| { println!("{:?}", x) };
caller.register(callback);
let foo = Foo { bar: 1, baz: 2 };
caller.invoke(&foo);
}
struct Caller<'a, T> {
callback: Box<dyn FnMut(&T) + 'a>,
}
impl<'a, T> Caller<'a, T> {
fn new() -> Self {
Caller {
callback: Box::new(|_| ()),
}
}
fn register(&mut self, cb: impl FnMut(&T) + 'a) {
self.callback = Box::new(cb);
}
fn invoke(&mut self, x: &T) {
(self.callback)(x);
}
}
One way to make this clearer would be to use the expanded definition of invoke
:
fn register<F>(&mut self, cb: F)
where
F: for<'b> FnMut(&'b T) + 'a
{
self.callback = Box::new(cb);
}
See also:
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.