Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/unix/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Rust 所有权、关闭、FNOCE:很多困惑_Rust_Closures_Move_Mutable_Borrowing - Fatal编程技术网

Rust 所有权、关闭、FNOCE:很多困惑

Rust 所有权、关闭、FNOCE:很多困惑,rust,closures,move,mutable,borrowing,Rust,Closures,Move,Mutable,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,

我有以下代码片段:

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
}

这里处理的是两种不同类型的闭包–
FnOnce
FnMut
。两种类型的闭包都有不同的调用约定

如果您将闭包定义为

let mut x = 32;
let g  = move || {
    x = 33;
    x
};
编译器将推断闭包的类型为
FnMut
。虽然闭包返回所拥有的变量
x
,但仍然可以多次调用它,因为
x
Copy
,因此编译器选择
FnMut
作为最通用的适用类型

调用
FnMut
闭包时,闭包本身由可变引用传递。这就解释了你的第一个问题——直接调用
g
是不可行的,除非你让它可变,否则你就不能对它进行可变引用。我也在这里含蓄地回答了你的第三个问题–
self
Fn
traits的调用方法中提到了闭包本身,它可以被认为是包含所有捕获变量的结构

调用
f(g)
时,将
FnMut
闭包
g
作为
FnOnce
闭包传递给
f()
。这是允许的,因为所有
FnOnce
都是
FnMut
的一个超级特性,所以实现
FnMut
的每个闭包也实现
FnOnce
。现在闭包已转换为
FnOnce
,它也根据
FnOnce
调用约定进行调用:

pub trait FnOnce<Args> {
    type Output;
    extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
}
发布一次{
类型输出;
extern“rust call”fn call_once(self,args:args)->self::Output;
}
在这种情况下,闭包是通过值传入的,因此调用使用闭包。您可以放弃您所拥有的任何价值的所有权——这不需要是可变的


通过
f()
调用
g
时可以多次调用它的原因是
g
Copy
。它只捕获单个整数,因此可以复制任意次数。每次调用
f()
都会创建一个新的
g
,当在
f()

中调用它时,就会使用它。需要了解
Fn*
trait家族的一些基本知识,才能了解闭包实际上是如何工作的。你有以下特点:

  • ,顾名思义,它只能运行一次。如果我们查看docs页面,我们会发现特质定义与您在问题中指定的几乎相同。不过,最重要的是以下几点:“call”函数接受
    self
    ,这意味着它使用实现
    FnOnce
    的对象,因此与任何将
    self
    作为参数的trait函数一样,它拥有对象的所有权
  • ,它允许捕获变量的变异,或者换句话说,它需要
    和mut self
    。这意味着,当你做一个
    move | |{}
    闭包时,它会将你引用的、在闭包范围之外的变量移动到闭包的对象中。闭包的对象具有不可度量的类型,这意味着它对于每个闭包都是唯一的。这确实会迫使用户接受某种类型的可变闭包版本,因此
    &mut impl FnMut()->()
    mut x:impl FnMut()->()
  • ,这通常被认为是最灵活的。这允许用户获取实现特征的对象的不可变版本。这个特性的“call”函数的函数签名是三个函数中最容易理解的,因为它只引用闭包,这意味着在传递或调用闭包时不需要担心所有权
要解决您个人的疑问:

  • 疑问1:如上所述,当您
    将某物移动到闭包中时,变量现在由闭包拥有。本质上,编译器生成的代码类似于以下伪代码:
默认情况下,它调用
FnMut()->usize
实现

  • 疑问2:这里发生的情况是,只要捕获的每个变量都是
    Copy
    ,这意味着生成的闭包将被复制到
    f
    ,因此
    f
    最终会获取它的
    副本。当您将
    f
    的定义改为使用
    FnMut
    时,您之所以会出现错误,是因为您面临与疑问1类似的情况:您试图调用一个函数,该函数接收
    &mut self
    ,而您将参数声明为
    c:T
    ,而不是
    mut c:T
    c:&mut T
    ,在
    FnMut
    的眼中,其中任何一个都符合
    &mut self
  • 最后,疑问3,
    self
    参数是闭包本身,它捕获或移动了一些变量到自身中,因此它现在拥有它们
你读过编译器的消息了吗?虽然消息说要使g可变并且确实允许代码编译,为什么不可变的g可以在f内部使用而不能在f外部使用?因为
FnOnce
具有内部可变性。@Stargateur“
FnOnce
具有内部可变性”–不,这不是真的。调用
FnOnce
会消耗闭包,但这与内部可变性无关。@Stargateur“内部可变性”是行话。它有一个明确的定义。当然,你可以用这个词来表示其他的东西,但是我不认为这对学习锈病的人是有帮助的。
pub trait FnOnce<Args> {
    type Output;
    extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
}
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.