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

How do I pass reference parameters to boxed closures?

提问人:ger 提问时间:5/11/2021 最后编辑:E_net4ger 更新时间:5/11/2021 访问量:275

问:

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

Rust 回调 闭包

评论


答:

5赞 E_net4 5/11/2021 #1

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

通过不将闭包传递给 ,编译器将能够正确推断回调的预期参数类型,包括引用生存期。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);
    }
}

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

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

另请参阅:

评论

0赞 user4815162342 5/11/2021
这是 HRTB 的一个用例,我们真的想将边界表示为 ?FF: for<'b, T: 'b> FnMut(T) + 'a
0赞 E_net4 5/11/2021
@user4815162342我想这可能会有所帮助,只要这种特征绑定也会流入接收者类型。
0赞 ger 5/12/2021
有没有办法仍然允许回调接收拥有的值作为参数?