Troubles understanding rust borrowing system

Viewed 256

I'm going thru the rustlings exercises but I don't seem to understand how borrow semantics works.

I created this simple example (check the playground):

fn main() {
    let mut vec0: Vec<i64> = Vec::new();

    let vec1 = &mut vec0;

    println!("{} has content `{:?}`", "vec0", vec0);

    println!("{} has content `{:?}`", "vec1", vec1);
}

which gives the following error:

   Compiling playground v0.0.1 (/playground)
error[E0502]: cannot borrow `vec0` as immutable because it is also borrowed as mutable
  --> src/main.rs:9:47
   |
7  |     let vec1 = &mut vec0;
   |                --------- mutable borrow occurs here
8  | 
9  |     println!("{} has content `{:?}`", "vec0", vec0);
   |                                               ^^^^ immutable borrow occurs here
10 | 
11 |     println!("{} has content `{:?}`", "vec1", vec1);
   |                                               ---- mutable borrow later used here

For more information about this error, try `rustc --explain E0502`.
error: could not compile `playground` due to previous error

I though that when borrowing a value the original binding kept the ownership, that is, after the let vec1 = &mut vec0; vec0 should still own the vec.

Moreover, I don't understand why on line 9 on println!("{} has content `{:?}`", "vec0", vec0) an immutable borrow occurs. Isn't the vec still owned by vec0?

3 Answers

You're correct that vec0 is still the owner of the the underlying memory.

The reason println! needs to borrow vec0 is that the println! macro needs to be able to read from the vector in order to print it, therefore it needs a read-only reference. A read-only reference is automatically created and goes out of scope inside this macro.

The reason that you are getting a compiler error here is that you are violating the borrow checker rules:

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

You can't have two mutable references to the same value at the same time. If you reorder the lines in your function like this:

fn main() {
    let mut vec0: Vec<i64> = Vec::new();
    println!("{} has content `{:?}`", "vec0", vec0);
    let vec1 = &mut vec0;
    println!("{} has content `{:?}`", "vec1", vec1);
}

, there will be no problem, because in that variant vec0 is not accessed after the moment a mutable reference to it was created.

If vec1 was a normal (immutable) reference, there would be no problem even with your original line order, because having multiple immutable (read-only) references is not a problem:

fn main() {
    let mut vec0: Vec<i64> = Vec::new();
    let vec1 = &vec0;
    println!("{} has content `{:?}`", "vec0", vec0);
    println!("{} has content `{:?}`", "vec1", vec1);
}

References are most often used for function parameters, when you don't want to move a value, but only pass a reference to it. Only when you need to change the underlying value inside the function, you need to make the reference mutable.

The best way to understand how println references it's arguments is by using cargo-expand which will show the result of macro expansion. For your code it expands to

#![feature(prelude_import)]
#[prelude_import]
use std::prelude::rust_2018::*;
#[macro_use]
extern crate std;
fn main() {
    let mut vec0: Vec<i64> = Vec::new();
    let vec1 = &mut vec0;
    {
        ::std::io::_print(::core::fmt::Arguments::new_v1(
            &["", " has content `", "`\n"],
            &match (&"vec0", &vec0) { // Immutable borrow
                _args => [
                    ::core::fmt::ArgumentV1::new(_args.0, ::core::fmt::Display::fmt),
                    ::core::fmt::ArgumentV1::new(_args.1, ::core::fmt::Debug::fmt),
                ],
            },
        ));
    };
    {
        ::std::io::_print(::core::fmt::Arguments::new_v1(
            &["", " has content `", "`\n"],
            &match (&"vec1", &vec1) { // Immutable borrow
                _args => [
                    ::core::fmt::ArgumentV1::new(_args.0, ::core::fmt::Display::fmt),
                    ::core::fmt::ArgumentV1::new(_args.1, ::core::fmt::Debug::fmt),
                ],
            },
        ));
    };
}

As you can see println takes immutable reference to its arguments.(Which means vec0 stills owns the vector). But the problem here is that Rust enforces "multiple readers or single writer" rule at compile time. As long as there is mutable reference to a value you cannot use the owner until the mutable reference goes away. Similarly as long as there is multiple shared references to value not even it's owner can modify it.

For example, this will compile,

fn main() {
    let mut vec0: Vec<i64> = Vec::new();

    {
        let vec1 = &mut vec0;
        println!("{} has content `{:?}`", "vec1", vec1);
    } // The mutable reference goes out of scope here.

    println!("{} has content `{:?}`", "vec0", vec0);
}
Related