Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does shadowing not release a borrowed reference?

Tags:

rust

It seems that shadowing a variable does not release the borrowed reference it holds. The following code does not compile:

fn main() {
    let mut a = 40;
    let r1 = &mut a;
    let r1 = "shadowed";
    let r2 = &mut a;
}

With the error message:

error[E0499]: cannot borrow `a` as mutable more than once at a time
 --> src/main.rs:5:19
  |
3 |     let r1 = &mut a;
  |                   - first mutable borrow occurs here
4 |     let r1 = "shadowed";
5 |     let r2 = &mut a;
  |                   ^ second mutable borrow occurs here
6 | }
  | - first borrow ends here

I would expect the code to compile because the first reference r1 is shadowed before borrowing the second reference r2. Obviously, the first borrow lives until the end of the block although it is no longer accessible after line 4. Why is that the case?

like image 762
MB-F Avatar asked Aug 14 '17 14:08

MB-F


2 Answers

TL;DR: Shadowing is about name-lookup, borrowing is about lifetimes.

From a compiler point of view, variables have no name:

fn main() {
    let mut __0 = 40;
    let __1 = &mut __0;
    let __2 = "shadowed";
    let __3 = &mut __0;
}

This is not very readable for a human being, so the language allows us to use descriptive names instead.

Shadowing is an allowance on reusing names, which for the lexical scope of the "shadowing" variable will resolve the name to the "shadowing" one (__2 here) instead of the "original" one (__1 here).

However just because the old one can no longer be accessed does not mean it no longer lives: Shadowing != Assignment. This is especially notable with different scopes:

fn main() {
    let i = 3;
    for i in 0..10 {
    }
    println!("{}", i);
}

Will always print 3: once the shadowing variable's scope ends, the name resolves to the original again!

like image 155
Matthieu M. Avatar answered Oct 21 '22 08:10

Matthieu M.


It's not like the original r1 ceases to exist after it becomes shadowed; consider the MIR produced for your code without the last line (r2 binding):

fn main() -> () {
    let mut _0: ();                      // return pointer
    scope 1 {
        let mut _1: i32;                 // "a" in scope 1 at src/main.rs:2:9: 2:14
        scope 2 {
            let _2: &mut i32;            // "r1" in scope 2 at src/main.rs:3:9: 3:11
            scope 3 {
                let _3: &str;            // "r1" in scope 3 at src/main.rs:4:9: 4:11
            }
        }
    }

    bb0: {
        StorageLive(_1);                 // scope 0 at src/main.rs:2:9: 2:14
        _1 = const 40i32;                // scope 0 at src/main.rs:2:17: 2:19
        StorageLive(_2);                 // scope 1 at src/main.rs:3:9: 3:11
        _2 = &mut _1;                    // scope 1 at src/main.rs:3:14: 3:20
        StorageLive(_3);                 // scope 2 at src/main.rs:4:9: 4:11
        _3 = const "shadowed";           // scope 2 at src/main.rs:4:14: 4:24
        _0 = ();                         // scope 3 at src/main.rs:1:11: 5:2
        StorageDead(_3);                 // scope 2 at src/main.rs:5:2: 5:2
        StorageDead(_2);                 // scope 1 at src/main.rs:5:2: 5:2
        StorageDead(_1);                 // scope 0 at src/main.rs:5:2: 5:2
        return;                          // scope 0 at src/main.rs:5:2: 5:2
    }
}

Note that when "shadowed" becomes bound (_3), it doesn't change anything related to the original r1 binding (_2); the name r1 no longer applies to the mutable reference, but the original variable still exists.

I wouldn't consider your example a very useful case of shadowing; its usual applications, e.g. bodies of loops, are much more likely to utilize it.

like image 21
ljedrz Avatar answered Oct 21 '22 06:10

ljedrz