我有以下代码片段:
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
在 Fn
、FnMut
和 FnOnce
特征定义中,self
指的是什么?那是闭包本身吗?还是环境?
例如。来自文档:
pub trait FnMut<Args>: FnOnce<Args> {
extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output;
}
最佳答案
了解闭包的实际工作原理需要一些关于 Fn*
trait 系列的基础知识。你有以下特点:
-
FnOnce
,顾名思义,它只能运行一次。如果我们查看文档页面,我们会发现特征定义与您在问题中指定的几乎相同。但最重要的是以下内容:“call”函数采用self
,这意味着它使用实现FnOnce
的对象,因此就像任何采用 a 的特征函数一样self
作为参数,它获取对象的所有权。 -
FnMut
,它允许对捕获的变量进行突变,或者换句话说,它需要&mut self
。这意味着,当您进行move 时 || {}
闭包,它会将您引用的任何在闭包范围之外的变量 move 到闭包的对象中。闭包的对象有一个不可命名的类型,这意味着它对每个闭包都是唯一的。这确实迫使用户采用闭包的某种可变版本,因此&mut impl FnMut() -> ()
或mut x: impl FnMut() -> ()
-
Fn
,这通常被认为是最灵活的。这允许用户采用实现特征的对象的不可变版本。此特征的“调用”函数的函数签名是这三个函数中最容易理解的,因为它只需要对闭包的引用,这意味着您在传递或调用它时无需担心所有权。
解决您个人的疑问:
- 疑问 1:如上所示,当您将某些内容
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
实现。
- 疑问 2:这里发生的是 closures are
Copy
只要他们捕获的每个变量都是Copy
,意味着生成的闭包将被复制到f
中,这样f
就结束了获取它的Copy
。当您将f
的定义更改为采用FnMut
时,您会收到错误,因为您面临着与怀疑 1 类似的情况:您正在尝试调用一个函数当您将参数声明为c: T
而不是mut c: T
或c: &mut 时,接收到
,在&mut self
TFnMut
的眼中,两者都符合&mut self
的条件。 - 最后,疑惑3,
self
参数是闭包本身,它已经捕获或 move 一些变量到自身,所以它现在拥有它们。
关于rust - 所有权,关闭,FnOnce : much confusion,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56743984/