如何将一个方法作为回调函数传递

4
在Python或C++中,一个类A可以将一些工作委托给另一个类B的实例,并在B中设置A的回调方法。我尝试在Rust中做到这一点,但是到目前为止,我被Rust编译器打败了。
以下是我尝试过的代码,请参见本文末尾的剩余代码。
在A::test中,我尝试使用闭包来获取一个Fn() trait对象作为回调。
// let b = B::new(self.finish)); // ideally but would not compile

// let test :Box<Fn(String)> = Box::new(move |msg| {self.finish(msg);}); // cannot infer an appropriate lifetime due to conflicting requirements
// let b = B::new(&test);

// let b = B::new(&Box::new( |msg| {A::finish(&self, msg);} )); // expected trait std::ops::Fn, found closure
// let b = B::new(&Box::new( |msg| {self.finish(msg);} )); // expected trait std::ops::Fn, found closure

目前还没有有效的解决办法。有没有其他方法可以实现这个想法?

非常感谢任何帮助!

或者我基本上是错的吗?Rust是否需要另一种方式来实现这里的想法?

以下是我的测试代码:

Play Ground 链接

struct A {}

impl A {
    fn finish(&self, msg: String) {
        println!("{}", msg);
    }

    fn test(&self) {

        //let b = B::new(self.finish)); // would not compile

        // let test :Box<Fn(String)> = Box::new(move |msg| {self.finish(msg);}); // cannot infer an appropriate lifetime due to conflicting requirements
        // let b = B::new(&test);

        // let b = B::new(&Box::new( |msg| {A::finish(&self, msg);} )); // expected trait std::ops::Fn, found closure
        let b = B::new(&Box::new( |msg| {self.finish(msg);} )); // expected trait std::ops::Fn, found closure

        b.start("hi".to_string().clone());
    }
}

struct B<'b> {
    // cb:fn(msg:String),
    cb: &'b Box<Fn(String)>,
}

impl<'b> B<'b> {
    fn new(cb: &'b Box<Fn(String)>) -> B<'b> {
        B { cb: cb }
    }

    fn start(&self, msg: String) {
        (self.cb)(msg);
    }
}

fn main() {
    let a = A {};
    a.test();
}

2
为什么所有的代码都被注释掉了?请不要这样做!同时,请在游乐场的右上角使用 rustfmt 或在您的机器上使用 cargo fmt - hellow
@hellow 感谢建议。我是 Rust 的新手,不知道这一点。Play Ground 是个绝妙的想法。 - Ian.Zhang
1个回答

4

是的,您可以将一个方法作为回调函数传递给您的结构体,并从该结构体的方法中调用它。而且,您不需要将闭包进行封装,只需传递引用即可:

struct A {}

impl A {
    fn finish(&self, msg: String) {
        println!("{}", msg);
    }

    fn test(&self) {
        let fun = |msg: String| self.finish(msg);
        let b = B::new(&fun);
        b.start("hi".to_string().clone());
    }
}

struct B<'b> {
    cb: &'b Fn(String),
}

impl<'b> B<'b> {
    fn new(cb: &'b Fn(String)) -> B<'b> {
        B { cb }
    }

    fn start(&self, msg: String) {
        (self.cb)(msg);
    }
}

fn main() {
    let a = A {};
    a.test();
}

演示平台

如果您要将函数移动到新的结构体中,那么box是很有用的,但这不是您的情况。

注意:由于您的函数名为start,我怀疑在您的实际使用场景中,您想要启动一个线程,在这种情况下,您应该考虑使用通道而不是回调函数。


谢谢,您能否更详细地介绍一下 Box<Fn()> 的用法? - Ian.Zhang
1
@Ian.Zhang 一个Box非常有用,可以存储非固定大小的值。如果一个Fn(String)结构体参数在编译时大小未知,那么你就需要将它装箱。如果你存储一个引用,那么就不需要装箱:引用具有恒定的大小。 - Denys Séguret
是的,最初我开了一个线程并尝试使用回调函数收集结果,但失败了。然后用通道解决了这个问题。我想知道回调函数是否也可以起作用。 - Ian.Zhang
2
简单而干净的解决方案是使用通道而不是尝试将回调传递给其他线程。 - Denys Séguret
我明白了,再次感谢。 - Ian.Zhang

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接