如何将引用参数传递给盒装闭包?

ger*_*ger 2 closures callback rust

我想存储一个回调,它可以采用不同类型的参数(拥有的值和引用),还可以修改其环境(因此是 FnMut)。当使用引用调用回调时,我希望编译器强制该参数仅在闭包主体中有效。我尝试使用盒装闭包来实现这一点。

下面显示了一个最小示例:

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,
}

Run Code Online (Sandbox Code Playgroud)

我想了解为什么如果我直接调用它会起作用,callback()但是如果我通过结构调用它而不是拥有闭包,编译器会抱怨生命周期。也许它与Box? 如果我foo之前定义caller,我可以让它工作,但我想避免这种情况。

E_n*_*ate 5

这是使用类似类型的闭包和边界时编译器类型推断怪癖的另一个示例(问题 #41078)。尽管这Caller<'a, T>似乎能够很好地处理invoke对给定 generic 的调用T,但给定的示例传递了一个引用&'b Foo'b该值的匿名生命周期在哪里)。并且由于这个限制,T被推断为具有&Foo一个预期生命周期的 a,这不同于任何生命周期对类型Foo( for<'a> &'a Foo)值的引用,并且与传递给invoke调用的引用不兼容。

通过不将闭包传递给Caller,编译器将能够正确推断回调的预期参数类型,包括引用生命周期。

克服此问题的一种方法是重新定义Caller以显式接收引用值作为回调参数。&T如上所述,这将推断类型的行为更改为更高等级的生命周期界限。

操场

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

使这一点更清楚的一种方法是使用 的扩展定义invoke

    fn register<F>(&mut self, cb: F)
    where 
        F: for<'b> FnMut(&'b T) + 'a
    {
        self.callback = Box::new(cb);
    }
Run Code Online (Sandbox Code Playgroud)

也可以看看: