简体   繁体   中英

Rust: Why can I not assign to `n` when it is borrowed?

The bellow code creates a variable n , then borrows n and names it nref , and then assigns a new value to the original n . Assignment has happened after a borrow, but all is fine, and the code compiles.

//example 1
fn main() {

    let mut n = 1;
    let nref = &n;
    n = 3;
}

The following code also compiles. This prints the value of the borrowed n .

//example 2
fn main() {

    let mut n = 1;
    let nref = &n;
    println!("{}", nref);
}

If I use a mutable borrow, I can even change the value of n via nref . This also compiles.

//example 3
fn main() {

    let mut n = 1;
    let nref = &mut n;
    *nref = 4;
    println!("{}", n);
}

However, this code does not compile:

//example 4
fn main() {

    let mut n = 1;
    let nref = &n;
    n = 3;
    println!("{}", nref);
}

It gives the error:

error[E0506]: cannot assign to `n` because it is borrowed
 --> src\main.rs:5:5
  |
4 |     let nref = &n;
  |                -- borrow of `n` occurs here
5 |     n = 3;
  |     ^^^^^ assignment to borrowed `n` occurs here
6 |     println!("{}", nref);
  |                    ---- borrow later used here

But in example 1 , we also assigned a value to n after it had been borrowed, and it was fine. Why does using it after the assignment cause a problem? What bugs is this trying to avoid?

How can I safely achieve what I am trying to do in example 4 ? I want a read only view of a number via it's reference, and I want to change the original, and then I want to look at the reference and see that the value has changed

edit

It seems I have to borrow n again. The bellow works:

//example 5
fn main() {              //line1
    let mut n = 1;       //line2
    let mut nref = &n;   //line3
    n = 4;               //line4
    nref = &n;           //line5
    println!("{}",nref); //line6
}

Does this mean that when line 4 is executed, the borrow from line 3 is no longer valid? And hence I have to re-borrow it on line 5, so that I can use it on line 6?

What is going on under the hood here? I come from a C++ background, so in my head n is stored at a memory location, and nref points to that memory location. When the value of n is changed, the memory location it is at does not change, so nref will see the new value. However the above suggests that when we assign a new value to n , the references to it are no longer valid. Does this mean it is acting more like Python, where everything is essentially a pointer to a bit of memory, and when you reassign something, you just repoint it at a new bit of memory?

Let's first explore the technical reason why this example doesn't compile:

//example 4
fn main() {
    let mut n = 1;
    let nref = &n;
    n = 3;
    println!("{}", nref);
}

Rust allows infinite immutable borrows or one mutable borrow. Assigning a value is effectively a mutable borrow, which isn't allowed because there's already an immutable borrow, causing an error.

However, this might seem counter-intuitive, because what's the worst that could happen? In this case, nothing. The borrow-checker has to reject some correct programs in order to always work. The issue arises when you start complicating the code. What if you use a Vec for heap allocation instead?

let mut v = vec![1, 2, 3];
let vref = &v;
v = vec![4, 5, 6, 7, 8, 9];

This would be problematic because vref is now pointing to the old Vec that got dropped. Or, what if you sent nref to another thread and were reading it at the same time as writing to it? That would also cause problematic behavior. This is why Rust doesn't allow you to do things like this.

Your last case is a "gotcha" by the Rust compiler. The compiler automatically shrinks the lifetime of borrows to the last use, and since you never use nref before reassigning it, the compiler shrinks the lifetime of the reference to nothing, meaning there are no conflicts:

//example 5
fn main() {              //line1
    let mut n = 1;       //line2
    let mut nref = &n;   //line3, nref is immediately dropped due to being unused
    n = 4;               //line4, no active references, this is legal
    nref = &n;           //line5, create a new reference
    println!("{}",nref); //line6
}

To quote the excellent Rust for Rustaceans

Whenever a reference with some lifetime 'a is used, the borrow checker checks that 'a is still alive . It does this by tracing the path back to where 'a starts---where the reference was taken---from the point of use and checking that there are no conflicting uses along that path. This ensures that the reference still points to a value that is safe to access. ... the compiler checks that the flow of the reference we are accessing does not conflict with any other parallel flows.

Basically, when you use (access) a reference, the borrow checker will check back through the lifetime of that reference back to when it was created and ensure that at no point during that "flow" a conflicting use of the reference exists. Conflicting can mean anything that breaks Rust's aliasing rules: any number of immutable references xor at most one mutable reference. As soon as you do something that breaks this rule, the original reference is no longer "alive", but rustc will not throw an error until you try to use that reference that is no longer "alive". Whether or not the variable name is in scope is not necessarily relevant.

Your examples where you might expect the borrow checker to complain, but it doesn't, comes down to the fact that you don't use those references after the conflicting uses: no usage no conflict.

Also, note that the lifetime is a property of the borrow not of the variable name . In your last example, you re-assign to nref , creating a new borrow with a new lifetime . Thus, there are no conflicts with the original borrow assigned to nref on line 3 because you never used that borrow.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM