简体   繁体   中英

How do I pass reference parameters to boxed closures?

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.

Playground

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM