Rust 中的闭包根据其捕获行为已经属于三个明确定义的类别之一(Fn、FnMut 和 FnOnce)。
如果只有这三种可能的类型,为什么还要麻烦 impl 呢?为什么不直接使用它们作为类型而不是 impl Trait?
如果只有这三种可能
这是不正确的。
Fn
、FnMut
和FnOnce
不是类型,它们是特质。事实上,它们不可能只是类型。
虽然您定义的任何普通 fn
都可以表示为一个简单的指针,但闭包是完全不同的事情。他们可以捕获他们的环境,这实际上意味着他们可以使用他们附近定义的变量。这也意味着他们需要将这些变量存储在某个地方,因为你无法真正知道闭包何时被执行。每个闭包可能想要捕获不同类型的变量,所有变量都有不同的大小,因此不可能有一种通用的类型可以包含您可能使用的任何类型的“上下文”。因此,通过编译器的魔法,创建了一个不可命名的类型,您可以使用简单的代码片段来确认:
let mut x = 5;
let f = || {
x += 1;
};
println!("size_of({}) = {}", std::any::type_name_of_val(&f), std::mem::size_of_val(&f));
此打印:
size_of(playground::main::{{closure}}) = 8
在幕后,上面代码中发生的事情与此接近:
let mut x = 5;
struct Closure<'a> {
// closure context
x: &'a mut i32,
}
impl FnOnce<()> for Closure<'_> {
type Output = ();
extern "rust-call" fn call_once(self, _args: ()) -> Self::Output {
// closure body
*self.x += 1;
}
}
let f = Closure { x: &mut x };
// now it can be called:
f();
本例中的闭包对象必须包含对其修改的变量的可变引用,因此闭包类型的长度为 8 个字节(在 64 位平台上)。您可能想尝试一下不同的闭包捕获不同的东西,并注意到闭包类型的大小直接取决于它捕获的变量。