为什么 Rust 编译器会出现“不能作为不可变借用,因为它也被作为可变借用"的错误?将变量移入作用域后? [英] Why does the Rust compiler error with "cannot borrow as immutable because it is also borrowed as mutable" after moving the variable into a scope?

查看:14
本文介绍了为什么 Rust 编译器会出现“不能作为不可变借用,因为它也被作为可变借用"的错误?将变量移入作用域后?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在阅读了 Rust 的作用域和引用之后,我编写了一个简单的代码来测试它们.

After reading about Rust's scopes and references, I wrote a simple code to test them.

fn main() {
    // 1. define a string
    let mut a = String::from("great");

    // 2. get a mutable reference
    let b = &mut a;
    b.push_str(" breeze");
    println!("b = {:?}", b);

    // 3. A scope: c is useless after exiting this scope
    {
        let c = &a;
        println!("c = {:?}", c);
    }

    // 4. Use the mutable reference as the immutable reference's scope
    //    is no longer valid.
    println!("b = {:?}", b);  // <- why does this line cause an error?
}

据我所知:

  • 不可同时使用不可变和可变.
  • 不能存在同一对象的 2 个可变变量.
    • 但作用域可以允许存在 2 个可变变量,只要 2 个可变变量不在相同的范围.

    3中,c是在一个作用域内创建的,其中没有使用任何变量.因此,当 c 超出范围时,很明显 c 将不再使用(如它是无效的),因此 b 是一个可变引用,可以安全地用于4.

    In 3, c is created within a scope, and no mutables are used in it. Hence, when c goes out of scope, it is clear that c will not be used anymore (as it would be invalid) and hence b, a mutable reference, can be used safely in 4.

    预期输出:

    b = "great breeze"
    c = "great breeze"
    b = "great breeze"
    

    现实

    Rust 产生以下错误:

    Reality

    Rust produces the following error:

    error[E0502]: cannot borrow `a` as immutable because it is also borrowed as mutable
      --> src/main.rs:12:17
       |
    6  |     let b = &mut a;
       |             ------ mutable borrow occurs here
    ...
    12 |         let c = &a;
       |                 ^^ immutable borrow occurs here
    ...
    18 |     println!("b = {:?}", b); // <- why does this line cause an error?
       |                          - mutable borrow later used here
    

    推理

    (这只是我认为正在发生的事情,可能是谬论)

    Inference

    (This is just what I think is happening, and can be a fallacy)

    似乎无论如何都不能使用任何可变引用(b)一旦一个不可变的引用(c)被创建(或被 Rust编译器),无论是否在范围内.

    It seems that no matter what, you cannot use any mutable references (b) once an immutable reference (c) was made (or "seen" by the rust compiler), whether in a scope or not.

    这很像:

    在任何给定时间,您可以拥有一个可变引用或任何不可变引用的数量.

    At any given time, you can have either one mutable reference or any number of immutable references.

    这种情况类似于:

    let mut a = String::from("great");
    
    let b = &mut a;
    b.push_str(" breeze");
    println!("b = {:?}", b);
    // ^ b's "session" ends here. Using 'b' below will violate the rule:
    // "one mutable at a time"
    
    // can use multiple immutables: follows the rule
    // "Can have multiple immutables at one time"
    let c = &a;
    let d = &a;
    println!("c = {:?}, d = {:?}", c, d);
    println!("b = {:?}", b); // !!! Error
    

    另外,只要我们使用不可变引用,原始对象或引用变为不可变的".如 Rust Book:

    Also, as long as we are using immutable references, the original object or reference becomes "immutable". As said in Rust Book:

    当我们有一个不可变的引用时,我们不能有一个可变的引用.不可变引用的用户不希望这些值突然出现从他们下面变出来!

    We also cannot have a mutable reference while we have an immutable one. Users of an immutable reference don’t expect the values to suddenly change out from under them!

    ...你可以有任何一个可变引用...

    ...you can have either one mutable reference...

    let mut a = String::from("great");
    
    let b = &mut a;
    b.push_str(" breeze");
    println!("b = {:?}", b);
    
    let c = &b; // b cannot be changed as long as c is in use
    b.push_str(" summer"); // <- ERROR: as b has already been borrowed.
    println!("c = {:?}", c); // <- immutable borrow is used here
    

    所以上面的这段代码在某种程度上解释了@Shepmaster 的解决方案.

    So this code above somewhat explains @Shepmaster's solution.

    回到原始代码并移除作用域:

    Going back to the original code and removing the scope:

    // 1. define a string
    let mut a = String::from("great");
    
    // 2. get a mutable reference
    let b = &mut a;
    b.push_str(" breeze");
    println!("b = {:?}", b);
    
    // 3. No scopes here.
    let c = &a;
    println!("c = {:?}", c);
    
    
    // 4. Use the mutable reference as the immutable reference's scope
    //    is no longer valid.
    println!("b = {:?}", b);  // <- why does this line cause an error?
    

    现在很清楚为什么这段代码有错误了.Rust 编译器看到我们使用的是可变的 b(它是 a 的可变引用,因此 a 变得不可变)同时还借用了一个不可变的参考 c.我喜欢称它为中间没有一成不变".

    Now it is clear why this code has an error. The rust compiler sees that we are using a mutable b (which is a mutable reference of a, therefore a becomes immutable) while also borrowing an immutable reference c. I like to call it "no immutables in between".

    或者我们也可以称之为非夹心".你不能拥有/使用可变的在不可变声明"之间和不变的使用"反之亦然.

    Or we can also call it "un-sandwiching". You cannot have/use a mutable between "immutable declaration" and "immutable use" and vice-versa.

    但这仍然没有回答为什么范围在这里失败的问题.

    But this still does not answer the question of why scopes fail here.

    • 即使在显式地将 c 移入作用域之后,为什么 Rust编译器产生这个错误信息?
    • Even after explicitly moving c into a scope, why does the Rust compiler produce this error message?

    推荐答案

    你的问题是为什么编译器不允许 c 引用已经可变借用的数据.我希望一开始就不允许这样做!

    Your question is why doesn't compiler allow c to refer to data which is already mutably borrowed. I for one would expect that to be disallowed to begin with!

    但是 - 当您注释掉最后一个 println!() 时,代码编译正确.大概这就是导致您得出允许别名的结论的原因,只要可变参数不在同一范围内".我认为这个结论是不正确的,原因如下.

    But - when you comment out the very last println!(), the code compiles correctly. Presumably that's what led you to conclude that aliasing is allowed, "as long as mutables aren't in the same scope". I argue that that conclusion is incorrect, and here is why.

    虽然在某些情况下确实允许子作用域中的引用使用别名,但它需要进一步的限制,例如通过结构体投影缩小现有引用.(例如,给定一个 let r = &mut point,您可以编写 let rx = &mut rx,即临时可变地借用可变借用数据的子集.)但是情况并非如此.这里的 c 是一个全新的共享引用,它已经被 b 可变引用了.这绝不应该被允许,但它可以编译.

    While it's true that there are some cases where aliasing is allowed for references in sub-scopes, it requires further restrictions, such as narrowing an existing reference through struct projection. (E.g. given a let r = &mut point, you can write let rx = &mut r.x, i.e. temporarily mutably borrow a subset of mutably borrowed data.) But that's not the case here. Here c is a completely new shared reference to data already mutably referenced by b. That should never be allowed, and yet it compiles.

    答案在于编译器对非词法生命周期 (NLL).当您注释掉最后一个 println!() 时,编译器会注意到:

    The answer lies with the compiler's analysis of non-lexical lifetimes (NLL). When you comment out the last println!(), the compiler notices that:

    1. b 不是 Drop,所以如果我们假装它更早被丢弃,也许是在上次使用后立即丢弃,没有人会观察到差异.

    1. b isn't Drop, so no one can observe a difference if we pretend it was dropped sooner, perhaps immediately after last use.

    b 在第一个 println!() 之后不再使用.

    b is no longer used after the first println!().

    所以NLL在第一个println!()之后插入了一个不可见的drop(b),从而允许首先引入c.只是因为 drop(b) 的隐式 c 不会创建可变别名.换句话说,b 的范围被人为地缩短了由纯 词法 分析确定的范围(它相对于 {>}),因此非词法生命周期.

    So NLL inserts an invisible drop(b) after the first println!(), thereby allowing introduction of c in the first place. It's only because of the implicit drop(b) that c doesn't create a mutable alias. In other words, the scope of b is artificially shortened from what would be determined by purely lexical analysis (its position relative to { and }), hence non-lexical lifetime.

    您可以通过将引用包装在新类型中来测试此假设.例如,这相当于你的代码,它仍然在编译时将最后一个 println!() 注释掉:

    You can test this hypothesis by wrapping the reference in a newtype. For example, this is equivalent to your code, and it still compiles with the last println!() commented out:

    #[derive(Debug)]
    struct Ref<'a>(&'a mut String);
    
    fn main() {
        let mut a = String::from("great");
    
        let b = Ref(&mut a);
        b.0.push_str(" breeze");
        println!("b = {:?}", b);
    
        {
            let c = &a;
            println!("c = {:?}", c);
        }
    
        //println!("b = {:?}", b);
    }
    

    但是,如果我们仅仅为Ref实现Drop,代码将不再编译:

    But, if we merely implement Drop for Ref, the code no longer compiles:

    // causes compilation error for code above
    impl Drop for Ref<'_> {
        fn drop(&mut self) {
        }
    }
    

    明确回答您的问题:

    即使将 c 显式移动到作用域中,为什么 Rust 编译器会产生此错误消息?

    Even after explicitly moving c into a scope, why does the Rust compiler produce this error message?

    因为 c 一开始就不允许与 b 一起存在,无论是内部作用域.当允许存在时,编译器可以证明b永远不会与c并行使用,并且可以安全地删除它甚至在构造 c 之前.在那种情况下,允许"混叠.因为尽管 b 在范围内",但没有实际的别名;- 在生成的 MIR/HIR 级别,只有 c 引用了数据.

    Because c is not allowed to exist alongside b to begin with, regardless of being an an inner scope. When it is allowed to exist is in cases where the compiler can prove that b is never used in parallel with c and it's safe to drop it before c is even constructed. In that case aliasing is "allowed" because there's no actual aliasing despite b "being in scope" - on the level of the generated MIR/HIR, it's only c that refers to the data.

    这篇关于为什么 Rust 编译器会出现“不能作为不可变借用,因为它也被作为可变借用"的错误?将变量移入作用域后?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆