如何在 Rust 的 struct 中存储闭包?

在 Rust 1.0之前,我可以使用这种过时的闭包语法编写一个结构:

struct Foo {
pub foo: |usize| -> usize,
}

现在我可以这样做:

struct Foo<F: FnMut(usize) -> usize> {
pub foo: F,
}

那么我创建的 Foo对象的类型是什么呢?

let foo: Foo<???> = Foo { foo: |x| x + 1 };

我还需要一个推荐人:

struct Foo<'a> {
pub foo: &'a mut FnMut(usize) -> usize,
}

我觉得这个比较慢,因为

  1. 指针解引用
  2. 实际上最终使用的 FnMut类型没有专门化
40123 次浏览

对于在第三个代码片段中使用的类型,有一个 不是; 闭包类型是匿名的,不能直接命名。相反,你会写道:

let foo = Foo { foo: |x| x + 1 };

如果你在写代码的上下文中使用 需要来指定你想要一个 Foo,你可以这样写:

let foo: Foo<_> = Foo { foo: |x| x + 1 };

_告诉类型系统为您推断实际的泛型类型。

按降序使用 哪个的一般经验法则:

  • 通用参数: struct Foo<F: FnMut(usize) -> usize>。这是最有效的,但这确实意味着特定的 Foo实例只能存储 闭包,因为每个闭包都有不同的具体类型。
  • 特征参考: &'a mut dyn FnMut(usize) -> usize。有一个指针间接,但是现在您可以存储对任何具有兼容调用签名的闭包的引用。
  • 装箱封闭: Box<dyn FnMut(usize) -> usize>。这涉及到在堆上分配闭包,但是您不必担心生存期。与引用一样,您可以存储具有兼容签名的任何闭包。

在 Rust 1.0之前

使用 ||语法的闭包是对存储在堆栈上的闭包的引用,使它们等效于 &'a mut FnMut(usize) -> usize。旧式的 proc是堆分配的,等价于 Box<dyn FnOnce(usize) -> usize>(您只能调用一次 proc)。

为了演示的目的,使用更多的代码来补充 现有的答案:

解封

使用泛型类型:

struct Foo<F>
where
F: Fn(usize) -> usize,
{
pub foo: F,
}


impl<F> Foo<F>
where
F: Fn(usize) -> usize,
{
fn new(foo: F) -> Self {
Self { foo }
}
}


fn main() {
let foo = Foo { foo: |a| a + 1 };
(foo.foo)(42);
    

(Foo::new(|a| a + 1).foo)(42);
}

装箱特征对象

struct Foo {
pub foo: Box<dyn Fn(usize) -> usize>,
}


impl Foo {
fn new(foo: impl Fn(usize) -> usize + 'static) -> Self {
Self { foo: Box::new(foo) }
}
}


fn main() {
let foo = Foo {
foo: Box::new(|a| a + 1),
};
(foo.foo)(42);
    

(Foo::new(|a| a + 1).foo)(42);
}

特征对象引用

struct Foo<'a> {
pub foo: &'a dyn Fn(usize) -> usize,
}


impl<'a> Foo<'a> {
fn new(foo: &'a dyn Fn(usize) -> usize) -> Self {
Self { foo }
}
}


fn main() {
let foo = Foo { foo: &|a| a + 1 };
(foo.foo)(42);
    

(Foo::new(&|a| a + 1).foo)(42);
}

函数指针

struct Foo {
pub foo: fn(usize) -> usize,
}


impl Foo {
fn new(foo: fn(usize) -> usize) -> Self {
Self { foo }
}
}


fn main() {
let foo = Foo { foo: |a| a + 1 };
(foo.foo)(42);
    

(Foo::new(|a| a + 1).foo)(42);
}

我创建的 Foo对象的类型是什么?

它是一种无法命名的自动生成类型。

我还可以使用引用[ ... ]得慢一些,因为[ ... ]指针 deref [ ... ]没有专门化

也许吧,但对打电话的人来说会容易得多。

参见: