【问题标题】:Why does 'add_assign' in a lambda function require marking the lambda function variable as mutable, but 'add' with manual assignment doesn't?为什么 lambda 函数中的“add_assign”需要将 lambda 函数变量标记为可变,而手动赋值的“add”则不需要?
【发布时间】:2021-12-29 22:46:56
【问题描述】:

为什么add函数mut关键字不是强制的,而add_assign是强制的?

use std::ops::{Add, AddAssign};

let mut ssa = String::new();
let rr = move || { // no mut for rr is needed
    ssa = ssa.add("ds");
    println!("{}", ssa);
};
rr();

let mut ssa = String::new();
let mut rr = move || { // mut is mandatory
    ssa.add_assign("ds");
    println!("{}", ssa);
};
rr();

【问题讨论】:

    标签: lambda rust


    【解决方案1】:

    一个闭包去糖化成一个实现Fn特征的匿名结构(std::ops::{Fn, FnOnce, FnMut}尽可能,查看更多in the docs。你的第一个闭包只实现FnOnce。你的第二个实现FnMut,也是)。

    要了解这里发生了什么,让我们自己对闭包进行脱糖(注意:正确的脱糖需要每晚进行一次,因为实现 Fn* 特征是不稳定的):

    struct Closure1 {
        ssa: String,
    }
    impl FnOnce<()> for Closure1 {
        type Output = ();
        extern "rust-call" fn call_once(mut self, (): ()) {
            self.ssa = Add::add(self.ssa, "ds");
            println!("{}", self.ssa);
        }
    }
    
    let /*mut*/ ssa = String::new();
    let rr = Closure1 { ssa };
    FnOnce::call_once(rr, ());
    
    struct Closure2<'a> {
        ssa: &'a mut String,
    }
    impl FnOnce<()> for Closure2<'_> {
        type Output = ();
        extern "rust-call" fn call_once(mut self, (): ()) {
            FnMut::call_mut(&mut self, ())
        }
    }
    impl FnMut<()> for Closure2<'_> {
        extern "rust-call" fn call_mut(&mut self, (): ()) {
            AddAssign::add_assign(&mut *self.ssa, "ds");
            println!("{}", self.ssa);
        }
    }
    let mut ssa = String::new();
    let mut rr = Closure2 { ssa: &mut ssa };
    FnMut::call_mut(&mut rr, ());
    

    Playround.

    我们这里有什么?

    第一个闭包分配给ssa;这意味着它需要控制它。因此,它被移入了闭包本身。这也是它只实现FnOnce的原因。

    另一方面,第二个闭包仅变异 ssa:它只需要一个&amp;mut 对其的引用。

    调用闭包时,第一个闭包实现FnOnce:调用时以FnOnce::call_once()调用;移动关闭。如果您尝试再次调用它,它将因“使用移动值”而失败。

    但是第二个闭包是用FnMut::call_mut() 调用的(因为编译器总是用最不“强”的特征调用),它需要&amp;mut self。这意味着它不会移动闭包——因此你可以调用它两次,但另一方面,你需要对闭包进行可变引用——所以它需要被标记为mut

    (如果您想知道为什么第一个闭包需要在移动它时将ssa 标记为mut,确实,请注意在脱糖中我注释掉了mut。但是,编译器知道闭包并认为闭包中的赋值需要mut 访问,因为如果不这样做会很混乱。)

    【讨论】:

      【解决方案2】:

      如果可能,闭包实现Fn,如果可能,实现FnMut,否则仅实现FnOnce。第一个带有ssa = ssa.add("ds") 的闭包只实现了FnOnce(由于ssa 被移出,如果add 发生恐慌并且恐慌被捕获,它不会被替换;因此在一般情况下它不能被调用两次) . ssa.add_assign("ds") 的第二个闭包实现了FnOnceFnMut

      当调用闭包时,编译器将使用Fn中的方法(带有&amp;self)如果已实现,否则使用FnMut中的方法(带有&amp;mut self)如果已实现,否则使用FnOnce中的方法(self)。只有在FnMut 的情况下才需要let mut

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-06-19
        • 2018-11-08
        • 1970-01-01
        • 1970-01-01
        • 2019-12-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多