所有权、关闭、FnOnce:很多混乱

fcr*_*r79 3 closures move mutable rust borrowing

我有以下代码片段:

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
}
Run Code Online (Sandbox Code Playgroud)

疑问1

我什至无法运行一次。

疑问2

...但我可以根据需要多次调用该闭包,只要我通过f. 有趣的是,如果我声明它FnMut,我会得到与疑问 1 相同的错误。

疑点3

和特征定义self中指的是什么?这就是关闭本身吗?还是环境?例如,来自文档:FnFnMutFnOnce

pub trait FnMut<Args>: FnOnce<Args> {
    extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output;
}
Run Code Online (Sandbox Code Playgroud)

Opt*_*ach 5

Fn*要理解闭包的实际工作原理,需要一些有关特征族的基础知识。你有以下特点:

  • FnOnce,顾名思义,只能运行一次。如果我们查看文档页面,我们会发现特征定义几乎与您在问题中指定的定义相同。但最重要的是:“call”函数采用self,这意味着它消耗实现 的对象FnOnce,因此与任何采用 aself作为参数的特征函数一样,它获取该对象的所有权。
  • FnMut,它允许捕获的变量发生突变,或者换句话说,它需要&mut self。这意味着,当您创建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.
Run Code Online (Sandbox Code Playgroud)

默认情况下它会调用FnMut() -> usize实现。

  • 疑问 2:这里发生的情况是,闭包的长度Copy与它们捕获的每个变量的长度一样Copy,这意味着生成的闭包将被复制到 中f,因此f最终会获取Copy其中的 a 。当您将 for 的定义更改f为 take anFnMut时,您会收到错误,因为您面临与疑问 1 类似的情况:您试图调用一个函数,该函数&mut self在您声明参数为c: T时而不是mut c: Tor c: &mut T,或者其中有资格&mut self在眼中FnMut
  • 最后,疑问3,self参数是闭包本身,它捕获或移动了一些变量到它自己中,所以它现在拥有它们。