如何在 Rust 中从 trait 获取函数指针?

3

我该如何克服这样的事情:

struct Test {
    foo: Option<fn()>
}

impl Test {
    fn new(&mut self) {
        self.foo = Option::Some(self.a);
    }

    fn a(&self) { /* can use Test */ }
}

我收到了这个错误:
error: attempted to take value of method `a` on type `&mut Test`
 --> src/main.rs:7:36
  |
7 |             self.foo = Option::Some(self.a);
  |                                          ^
  |
  = help: maybe a `()` to call it is missing? If not, try an anonymous function

如何从trait中传递函数指针?类似于以下情况:

impl Test {
    fn new(&mut self) {
        self.foo = Option::Some(a);
    }
}

fn a() { /* can't use Test */ }
2个回答

5
你在尝试从绑定方法中获取一个函数指针(使用Python术语,因为Rust没有这个词)。但是你做不到。
首先,因为Rust没有“绑定”方法的概念;也就是说,你不能引用已经绑定了调用者(左侧的东西)的方法。如果你想构建一个类似的可调用对象,你可以使用闭包;即|| self.a()

然而,这仍然行不通,因为闭包并不是函数指针。像其他某些语言中一样,没有可调用对象的“基础类型”。函数指针是一种单一而特定的可调用对象,而闭包则完全不同。相反,有特征(traits)来使得一种类型可以被调用。这些特征包括 Fn, FnMut, 和 FnOnce。因为它们是特征,你不能将它们用作类型,必须在一些间接的层次下使用它们,例如 Box<FnOnce()>&mut FnMut(i32) -> String

现在,您可以Test更改为存储Option<Box<Fn()>>,但这仍然没有帮助。那是因为另一个问题:其他问题:您正在尝试在结构体内部存储对其自身的引用。这不会很好地工作。如果您成功做到这一点,您实际上会使Test值永久无法使用。更有可能的是,编译器根本不会让您走到那一步。

旁注:您可以这样做,但需要使用引用计数和动态借用检查,这超出了本文档的范围。

因此,按照提问的方式回答您的问题的答案是:您不需要这样做。
让我们改变问题:与其试图将自我引用闭包强行塞入其中,我们可以存储一个不尝试捕获调用者的可调用项。
struct Test {
    foo: Option<Box<Fn(&Test)>>,
}

impl Test {
    fn new() -> Test {
        Test {
            foo: Option::Some(Box::new(Self::a)),
        }
    }

    fn a(&self) { /* can use Test */ }

    fn invoke(&self) {
        if let Some(f) = self.foo.as_ref() {
            f(self);
        }
    }
}

fn main() {
    let t = Test::new();
    t.invoke();
}

被存储的可调用对象现在是一个显式接受调用者的函数,避开了循环引用的问题。我们可以使用这个来直接存储Test::a,通过将其视为自由函数进行引用。同时请注意,因为Test是实现类型,我也可以将其称为Self。
此外,我还纠正了你的Test::new函数。Rust没有构造函数,只有像其他函数一样返回值的函数。
如果你确信永远不想在foo中存储闭包,你可以将Box替换为fn(&Test)。这限制了你的功能指针,但避免了额外的分配。
如果你还没有阅读过Rust Book,我 强烈建议你阅读the Rust Book

3
您的代码存在一些错误。按照惯例,新函数不应该带有self引用,因为它被期望创建Self类型。
但是真正的问题在于Test::foo期望一个函数类型fn(),但是Test::a的类型是fn(&Test) == fn a(&self),如果您将foo的类型更改为fn(&Test),它将工作。同时,您需要使用trait名称而非self来使用函数名。您应该分配Test::a而非self.a。
以下是可行的版本:
extern crate chrono;

struct Test {
    foo: Option<fn(&Test)>
}

impl Test {
    fn new() -> Test {
        Test {
            foo: Some(Test::a)
        }
    }

    fn a(&self) {
        println!("a run!");
    }
}

fn main() {
    let test = Test::new();
    test.foo.unwrap()(&test);
}

如果你要在new()函数中分配一个字段,并且该值必须始终设置,那么就不需要使用Option,可以像这样:

extern crate chrono;

struct Test {
    foo: fn(&Test)
}

impl Test {
    fn new() -> Test {
        Test {
            foo: Test::a
        }
    }

    fn a(&self) {
        println!("a run!");
    }
}

fn main() {
    let test = Test::new();
    (test.foo)(&test); // Make sure the paranthesis are there
}

此外,您还可以在特质(traits)和实现(impls)中使用 Self(大写S)。例如,在您的情况下,您可以将 new 函数的返回类型更改为 Self,而不是重复使用 Test - undefined

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