我得到的解释是否正确(幕后的 Rust 代码需要在调用时消耗变量)?

问题描述 投票:0回答:1

我在理解闭包方面遇到了一些困难,所以我跳到一个论坛上询问一些有关幕后情况的问题。有人给我举了这个例子:

对于以下代码:

let x = String::new();
let f = || { println!("{x}") };
f();

下面的代码是 Rust 在幕后生成的(这只是一个 近似值,它并没有真正运行):

struct UnnameableClosureType<'a> {
    x0: &'a String,
}

impl<'a> Fn<()> for UnnameableClosureType<'a> {
    type Output = ();

    extern "rust-call" fn call(&self, args: Args) -> Self::Output {
        println!("{}", self.x0);
    }
}

impl<'a> FnMut<()> for UnnameableClosureType<'a> {
    type Output = ();

    extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output {
        self.call(args)
    }
}

impl<'a> FnOnce<()> for UnnameableClosureType<'a> {
    type Output = ();

    extern "rust-call" fn call_once(mut self, args: Args) -> Self::Output {
        self.call_mut(args)
    }
}

let x = String::new();
let f = UnnameableClosureType {
    x0: &x
};
f();

由于这需要在调用时消耗 x,所以唯一的特征 可以实现的是 FnOnce,而不是 Fn 或 FnMut。

这就是我得到的解释。

我不太明白的是编译器如何知道 FnOnce 是唯一可以实现的特征。

我尝试建议,如果输入变量(这是一个移动值)移动到

&self.x0
然后删除,就会有一个悬空引用
&self.x0
,但我不确定这是否正确,甚至这对我来说听起来有点奇怪,因为它描述了我们想要避免的结果(即使如此,我也不确定这是否真的会发生)而不是编译器使用的规则。

我得到的解释是否正确,我的理解是否合理?

rust struct closures
1个回答
0
投票

我不太明白的是编译器如何知道 FnOnce 是唯一可以实现的特征。

那是因为事实并非如此。解释是错误的;给定的闭包实现了所有三个;

Fn
FnMut
FnOnce

我不确定编译器生成的确切代码,但其背后的原理如下:

  • 默认情况下,每个闭包都会实现
    Fn
    FnMut
    FnOnce
    ,除非有什么阻碍。例子:
    // Fn + FnMut + FnOnce
    let f = |x| 2*x;
    
  • 如果闭包执行需要
    &mut
    的操作,它将不再实现
    Fn
    ,因为
    Fn
    只能执行不可变的操作。例子:
    let mut sum = 0;
    // FnMut + FnOnce
    let f = |x| sum += x;
    
  • 如果闭包执行需要获取某物所有权的操作,它将不再实现
    FnMut
    Fn
    ,因为这两者都不允许消费东西。例子:
    let mut s = String::new();
    // Only FnOnce, because `drop` consumes `s`
    let f = || drop(s);
    

此外,请注意,闭包是否是

move
并不影响它实现哪些特征。这仅决定闭包是否
'static
或是否具有生命周期。 决定这些特征的是它如何使用这些捕获 - 例如,如果它调用此类捕获对象的
&mut self
函数,则会删除
Fn
特征,因为
Fn
闭包不能改变事物。

© www.soinside.com 2019 - 2024. All rights reserved.