我有以下代码片段:

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
selfFnFnMutFnOnce 特征定义中指的是什么?那是闭包本身吗?还是环境?
例如。从文档:
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 的对象的不可变版本。这个特征的“调用”函数的函数签名是三个中最容易理解的,因为它只需要一个对闭包的引用,这意味着你在传递或调用它时不需要担心所有权。

  • 解决您的个人疑问:
  • 疑点 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 类似的情况:您正在尝试调用接收 &mut self 的函数,而您已将参数声明为 c: T 而不是无论是 mut c: T 还是 c: &mut T ,在 &mut self 眼中,这两者都符合 FnMut 的条件。
  • 最后,疑点3,self 参数是闭包本身,它已经捕获或 move 了一些变量到自身中,因此现在拥有它们。
  • 关于rust - 所有权,关闭,FnOnce : much confusion,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/56743984/

    10-13 05:16