25

Why do I need rebinding/shadowing when I can have mutable variable binding? Consider:

let x = a();
let x = b(x);

vs.

let mut x = a();
x = b(x);

Mutable variable binding allows a mutable borrow of that variable over this. But does shadowing have some advantages over mutable bindings?

Roman Polach
  • 447
  • 4
  • 11

2 Answers2

38

Because the two have totally different effects.


To really understand what is going on, we need to start at the beginning: what is a binding? What does binding mean?

Let's consider a simple function: fn hello() -> String;.

When invoking this function like so:

fn main() {
    hello();
}

What happens?

The function returns a String, which is promptly discarded (executing Drop as it is thereby freeing its memory).

The result is dropped because it was not bound to a variable name, and the rules of the language say that if not bound then it can be promptly dropped1.

If we bind this result, however, we prolong the life of this value, and we can access it via this binding... for a while.

fn main() {
    let value = hello();

    std::mem::drop(value);

    println!("{}", value); // Error: moved out of value
}

This is the issue at hand: in Rust, the lifetime of a value is independent from the scope of a binding.

A value need not even be dropped before its binding exits its scope: it can be transferred to another (similar to returning from a function).

fn main() {
    let x;
    {
        let y = hello();
        x = y;
    }
    println!("{}", x);
}

1 the same happens if binding to _.


So, now we armed with the fact that bindings and values differ, let's examine the two snippets.

A first shadowing snippet, differing from yours:

fn main() {
    let x = a();
    let x = b();
}

Steps, in order:

  • The expression a() creates a value, which is bound to x
  • The expression b() creates a value, which is bound to x
  • The value created by b() is dropped
  • The value created by a() is dropped

Note that the fact that x is re-bound does not affect the lifetime of the value that was previously bound.

Technically, it behaves exactly as if the result of b() was bound to y, with the sole exception that the previous x binding is not accessible while y is in scope.

Now, the mutable snippet:

fn main() {
    let mut x = a();
    x = b();
}

Steps, in order:

  • The expression a() creates a value, which is bound to x
  • The expression b() creates a value, which is bound to x, and the previous value (created by a()) is dropped
  • The value created by b() is dropped

Once again, accessing the previous value is impossible, however whilst with shadowing it's impossible temporarily (if shadowing in a smaller scope), with assignment it's impossible forever since the value is dropped.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • 1
    A wonderful answer that made the light bulb finally switch on. Shouldn't `x = y;` within the function `fn main() { let x; { let y = hello(); x = y; } println!("{}", x); }` be `let x = y;` Also, can you please clarify to newbies like me why value created by 'b' is dropped before the value created by 'a'. Trying to make sure I absorb this fully. – Kayote Jan 14 '18 at 11:59
  • 1
    @Kayote: (1) no I really mean `x = y;`. `let x;` is a way to declare a binding in advance without actually binding it (yet). This is used here to have the value assigned to `y` *escape* its scope. (2) at the end of a lexical scope, the variables are destroyed in *reverse order* of their creation; this is necessary so that a variable can *refer* to one declared before it safely. For example, consider `let vec = vec!(1, 2); let x = &vec[0];`: if `vec` were destroyed first, `x` would point to freed memory! – Matthieu M. Jan 14 '18 at 13:32
  • Prefect & makes sense. Thank you again. – Kayote Jan 14 '18 at 20:54
  • 1
    Does it mean that shadowing could lead to some kind of memory leak? In the snippet `fn main() { let x = a(); let x = b(); }`, the value created by `a()` could last till the end of the process, while there is no other place using it at all. – Eric Zheng May 07 '18 at 16:37
  • @EricZheng: Yes and no. The value created by `a()` will live until the end of the lexical scope, I'll leave it up to you to decide whether this constitute a memory leak or not in your case. To reason about, you can use the idea of *frame slots* and rewrite the code above to `fn main() {`, `frame [0] = a();`, `x = 0;`, `frame[1] = b();`, `x = 1;`, `}`. `x` is just the name of the frame slot in which the value lives; if you reassign the name you may not be able to access the value any longer, but the compiler still can, because it's got access to the stack frame itself. – Matthieu M. May 07 '18 at 17:11
  • 2
    Maybe it's just me, but this answer shows how the two are different and how they work; I'm no closer to understanding ***why*** the feature was built into the language in the first place, especially when you could've easily said `let x = a(); let y = b();` instead of using shadowing to re-bind `x` twice. – code_dredd May 12 '19 at 21:21
  • 1
    @code_dredd: This answer assumes having the same name is desirable, since in the OP the same name is reused. The question of whether shadowing is desirable instead of just using a different name is a separate question that all language designers have to think about; although I'm not sure how it would be received on StackOverflow, it's certainly a question that interests me. – Matthieu M. May 13 '19 at 07:51
14

One answer I found myself: shadowing can change variable type.

let x = get_some_string();
let x = x.smart_parse_int();
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Roman Polach
  • 447
  • 4
  • 11
  • 7
    Not only that. You also can change mutability (which is common usecase): `let mut foo = Foo::new(); /* build foo */ let foo = foo; // make foo immutable`. – Hauleth Nov 15 '16 at 23:37
  • 1
    @ŁukaszNiemier: Note that you can simply use block expressions for that, `let foo = { let mut foo = Foo::new(); ...; foo };` – Matthieu M. Nov 16 '16 at 07:37