我有以下代码片段:
fn f<T: FnOnce() -> u32>(c: T) {
println!("Hello {}", c());
}
fn main() {
let mut x = 32;
let g = move || {
x = 33;
x
};
g(); // Error: cannot borrow as mutable. Doubt 1
f(g); // Instead, this would work. Doubt 2
println!("{}", x); // 32
}
怀疑 1
我什至不能运行我的关闭一次。
怀疑 2
...但我可以根据需要多次调用该闭包,前提是我通过
f
调用它。有趣的是,如果我声明它 FnMut
,我会得到与怀疑 1 相同的错误。疑点 3
self
在 Fn
、 FnMut
和 FnOnce
特征定义中指的是什么?那是闭包本身吗?还是环境?例如。从文档:
pub trait FnMut<Args>: FnOnce<Args> {
extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output;
}
最佳答案
需要了解 Fn*
trait 系列的一些基础知识才能理解闭包的实际工作方式。你有以下特点:
FnOnce
,顾名思义,只能运行一次。如果我们查看文档页面,我们会看到特征定义与您在问题中指定的几乎相同。但最重要的是以下内容:“调用”函数采用 self
,这意味着它使用实现 FnOnce
的对象,因此就像任何采用 self
作为参数的特征函数一样,它获取对象的所有权。 FnMut
,它允许对捕获的变量进行变异,或者换句话说,它需要 &mut self
。这意味着,当您创建 move || {}
闭包时,它会将您引用的任何超出闭包范围的变量 move 到闭包的对象中。闭包的对象具有不可命名的类型,这意味着它对于每个闭包都是唯一的。这确实迫使用户采用某种可变版本的闭包,因此 &mut impl FnMut() -> ()
或 mut x: impl FnMut() -> ()
Fn
,通常被认为是最灵活的。这允许用户获取实现 trait 的对象的不可变版本。这个特征的“调用”函数的函数签名是三个中最容易理解的,因为它只需要一个对闭包的引用,这意味着你在传递或调用它时不需要担心所有权。 解决您的个人疑问:
move
将某些内容写入闭包时,该变量现在归闭包所有。本质上,编译器生成的内容类似于以下伪代码:struct g_Impl {
x: usize
}
impl FnOnce() -> usize for g_Impl {
fn call_once(mut self) -> usize {
}
}
impl FnMut() -> usize for g_Impl {
fn call_mut(&mut self) -> usize {
//Here starts your actual code:
self.x = 33;
self.x
}
}
//No impl Fn() -> usize.
默认情况下,它调用
FnMut() -> usize
实现。Copy
只要它们捕获的每个变量都是 Copy
,这意味着生成的闭包将被复制到 f
中,因此 f
最终会获取它的 Copy
。当您将 f
的定义更改为采用 FnMut
时,您会收到错误消息,因为您面临与怀疑 1 类似的情况:您正在尝试调用接收 &mut self
的函数,而您已将参数声明为 c: T
而不是无论是 mut c: T
还是 c: &mut T
,在 &mut self
眼中,这两者都符合 FnMut
的条件。 self
参数是闭包本身,它已经捕获或 move 了一些变量到自身中,因此现在拥有它们。 关于rust - 所有权,关闭,FnOnce : much confusion,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/56743984/