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

How do I pass reference parameters to boxed closures?

我想存储一个回调,它可以接受不同类型的参数(拥有的值和引用),还可以修改它的环境(因此是 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,
}

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

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

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

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

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

使这一点更清楚的一种方法是使用 invoke:

的扩展定义
    fn register<F>(&mut self, cb: F)
    where 
        F: for<'b> FnMut(&'b T) + 'a
    {
        self.callback = Box::new(cb);
    }

另请参阅: