简体   繁体   English

为什么在将变量移动到 scope 后 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?

After reading about Rust's scopes and references, I wrote a simple code to test them.在阅读了 Rust 的作用域和引用之后,我编写了一个简单的代码来测试它们。

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?
}

As far as I understand:据我所理解:

  • Immutables and mutables cannot be used simultaneously.不可变和可变不能同时使用。
  • 2 mutables of same object cannot exist.不能存在相同 object 的 2 个可变变量。
    • But scoping can allow 2 mutable to exist as long as 2 mutables are not in the same scope.但是,只要 2 个可变变量不在同一个 scope 中,作用域就可以允许 2 个可变变量存在。

Expectation期待

In 3 , c is created within a scope, and no mutables are used in it.3中, c是在 scope 中创建的,其中没有使用任何可变对象。 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 .因此,当c超出 scope 时,很明显c将不再使用(因为它将无效),因此可以在b 4中安全地使用 mutable 引用。

The expected output:预期的 output:

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

Reality现实

Rust produces the following error: Rust 产生以下错误:

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) (这正是我认为正在发生的事情,并且可能是一个谬误)

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.似乎无论如何,一旦生成了(或被 rust 编译器“看到”)不可变引用( c ),您就不能使用任何可变引用( b ),无论是在 Z31A1FD140BE4BEF2D18ZA112A 中还是不在。

This is much like:这很像:

At any given time, you can have either one mutable reference or any number of immutable references.在任何给定时间,您都可以拥有一个可变引用或任意数量的不可变引用。

This situation is similar to:这种情况类似于:

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

Also, as long as we are using immutable references, the original object or reference becomes "immutable".此外,只要我们使用不可变引用,原来的 object 或引用就会变成“不可变的”。 As said in Rust Book :Rust 书中所述:

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!不可变引用的用户不要期望值会突然从它们下面改变!

and

...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

So this code above somewhat explains @Shepmaster's solution.所以上面的这段代码在一定程度上解释了@Shepmaster 的解决方案。

Going back to the original code and removing the scope:回到原始代码并删除 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?

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 . rust 编译器看到我们正在使用可变的b (这是 a 的可变引用,因此a变为不可变),同时还借用a不可变引用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 .但这仍然没有回答为什么作用域在这里失败的问题。

Question问题

  • Even after explicitly moving c into a scope, why does the Rust compiler produce this error message?即使在将c显式移动到 scope 之后,为什么 Rust 编译器会产生此错误消息?

Your question is why doesn't compiler allow c to refer to data which is already mutably borrowed.您的问题是为什么编译器不允许c引用已经可变借用的数据。 I for one would expect that to be disallowed to begin with!我一开始就认为这是不允许的!

But - when you comment out the very last println!() , the code compiles correctly.但是 - 当您注释掉最后一个println!()时,代码会正确编译。 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.我认为这个结论是不正确的,这就是原因。

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.虽然确实在某些情况下允许对子作用域中的引用使用别名,但它需要进一步的限制,例如通过结构投影缩小现有引用。 (Eg given a let r = &mut point , you can write let rx = &mut r.x , ie temporarily mutably borrow a subset of mutably borrowed data.) But that's not the case here. (例如,给定let r = &mut point ,您可以写let rx = &mut r.x ,即临时可变地借用可变借用数据的子集。)但这里不是这种情况。 Here c is a completely new shared reference to data already mutably referenced by b .这里c是对b已经可变引用的数据的全新共享引用。 That should never be allowed, and yet it compiles.这绝不应该被允许,但它可以编译。

The answer lies with the compiler's analysis of non-lexical lifetimes (NLL).答案在于编译器对非词法生命周期(NLL) 的分析。 When you comment out the last println!() , the compiler notices that:当您注释掉最后一个println!()时,编译器会注意到:

  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不是Drop ,所以如果我们假装它被丢弃得更快,也许在最后一次使用后立即被丢弃,没有人可以观察到差异。

  2. b is no longer used after the first println!() . b在第一次println!()之后不再使用。

So NLL inserts an invisible drop(b) after the first println!() , thereby allowing introduction of c in the first place.因此 NLL 在第一个println!() ) 之后插入了一个不可见的drop(b) ,从而首先允许引入c It's only because of the implicit drop(b) that c doesn't create a mutable alias.只是因为隐含的drop(b)c不会创建可变别名。 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.换句话说, b的 scope 被人为地缩短了从纯词法分析(其 position 相对于{} )确定的内容,因此非词法寿命。

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:例如,这等效于您的代码,并且它仍然在最后一个println!()注释掉的情况下编译:

#[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);
}

But, if we merely implement Drop for Ref , the code no longer compiles:但是,如果我们只为Ref实现Drop ,则代码不再编译:

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

To explicitly answer your question:要明确回答您的问题:

Even after explicitly moving c into a scope, why does the Rust compiler produce this error message?即使在将c显式移动到 scope 之后,为什么 Rust 编译器会产生此错误消息?

Because c is not allowed to exist alongside b to begin with, regardless of being an an inner scope.因为c从一开始就不允许与b一起存在,无论是内部 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.允许存在的情况编译器可以证明b从未与c并行使用,并且在c甚至构造之前将其删除是安全的。 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.在这种情况下,混叠是“允许的”,因为尽管b “在范围内”,但没有实际的混叠 - 在生成的 MIR/HIR 级别上,只有c引用数据。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 Rust Inspect Iterator:不能将`*`借用为不可变的因为它也被借用为可变的 - Rust Inspect Iterator: cannot borrow `*` as immutable because it is also borrowed as mutable 不能作为不可变借用,因为它也作为可变借用(Rust) - Cannot borrow as immutable because it is also borrowed as mutable (Rust) 不能借用不可变的因为它也被借用为可变的 - cannot borrow as immutable because it is also borrowed as mutable 不能借用为不可变的,因为它也借用为可变的 - Cannot borrow as immutable because it also borrowed as mutable 不能借用 `...` 作为可变的,因为它也被借用为不可变的 - cannot borrow `…` as mutable because it is also borrowed as immutable 不能借用为可变的,因为它也被借用为不可变的 - Cannot borrow as mutable because it is also borrowed as immutable 错误:不能借用......作为不可变的,因为它也被借为可变的 - error: cannot borrow … as immutable because it is also borrowed as mutable 不能将 x 作为可变借用,因为它也作为不可变错误借用 - cannot borrow x as mutable because it is also borrowed as immutable error 当对象上的特征没有时,为什么引用上的特征会引发“不能借用为可变的,因为它也被借用为不可变的”? - Why does a trait on a reference raise "cannot borrow as mutable because it is also borrowed as immutable" when a trait on an object does not? 借用检查器:不能借为不可变的,因为它也借为可变的 - Borrow Checker: Cannot borrow as immutable because it is also borrowed as mutable
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM