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

Is This Explanation I Was Given (Rust Code Behind The Scenes Requires Consumption of Variable Upon Invocation) Correct?

提问人:Anon Anon 提问时间:10/22/2023 最后编辑:Anon Anon 更新时间:10/22/2023 访问量:73

问:

我在理解闭包方面遇到了一些困难,所以我跳到一个论坛上,问一些关于引擎盖下发生的事情的问题。有人给我举了这个例子:

对于以下代码:

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 结构 闭包

评论

1赞 JMAA 10/22/2023
这个解释在我看来很好。但是,您开始谈论您没有向我们展示的代码(什么是?),因此,如果您不向我们提出完整的上下文问题,我们将无法进一步提供帮助。z
0赞 Anon Anon 10/22/2023
@JMAA,我的错误,应该是“消费”。我相应地编辑了这个问题以反映这一点。x
0赞 Anon Anon 10/22/2023
@JMAA,我不明白的是,在调用时要求使用 x 如何导致编译器知道 FnOnce 是唯一可以实现的特征。
1赞 Finomnis 10/22/2023
不确定这是否有帮助,但这是我不久前写的一篇关于 和 的区别的帖子。FnFnMutFnOnce
3赞 Finomnis 10/22/2023
“由于这需要在调用时消耗 x,因此唯一可以实现的特征是 FnOnce,而不是 Fn 或 FnMut。”- 这句话是错误的。示例中的闭包不捕获 ,它只捕获 。此外,它不会下降.因此,它与所有这三个特征兼容,如图所示。请注意,即使它通过添加关键字来捕获,它仍然会实现所有这三个,因为它没有副作用。x&xxxmove

答:

5赞 Finomnis 10/22/2023 #1

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

那是因为它没有。解释是错误的;给定的闭包实现了所有三个;和。FnFnMutFnOnce

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

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

此外,请注意,闭包是否闭包不会影响它实现哪些特征。决定这些特征的是它如何使用这些捕获 - 例如,如果它调用此类捕获对象的函数,则会删除该特征,因为闭包可以记录事物的变异。move&mut selfFnFn