Not getting an error for immutability using `let y` twice

Following the Rust book Variables and Mutability - The Rust Programming Language but not getting the expected error out of the following assignments.

fn main() {
    let y = 5;
    println!("y is {}", y);

    let y = 7;
    println!("y is {}", y);
}

#     Finished dev [unoptimized + debuginfo] target(s) in 0.01s
#     Running `target/debug/variables`
#     y is 5
#     y is 7

Any ideas why? Is the book not up to date?

Cheers,

You have to remove let from the second assignment to get an error. Otherwise you are just defining a new variable, which is "shadowing" the old variable of the same name.

3 Likes

Dang easy one I missed it. Thanks!
I guess I'll need to understand why using mut is necessary for other areas now.

EDIT: This actually brings up a lot of questions! My understanding is I can create as many variables as I want with the same name (snippet above) but each will have its own pointer in memory.

The mut keyword is to make it clear to anyone reading the code that the variable is going to be mutated ( you get a warning if you declare it mut but don't actually mutate it). The compiler knows, and some people have even suggested it might be made optional, but I doubt that idea would gain any traction.

1 Like

Indeed each will have its own place in memory. A shadowed variable can still exist, it just cannot be named anymore. If you keep a reference to it, you can still see that it's there and has the same value unaltered. E. g. try

fn main() {
    let y = 5;
    println!("y is {}", y);
    
    let r = &y; // reference to y

    let y = 7;
    println!("y is {}", y);

    println!("value behind r is {}", r);
}

What an actual mutable variable allows you to do that can't be achieved with a new let are things like

  • efficiently modify just a part of the value

    e. g. you could have a large array let mut a = [0; 1000] and modify just one entry a[500] += 1 without needing to copy/move the whole array.

  • do an unlimited amount of mutations, i. e. mutate something in a loop

    a value introduces with just let can't really be observed anymore once you jump back in your code with a loop { ... }, you need mutable variables to write any sensible loop (that doesn't rely on outside world interaction). The same goes for while loops; and for loops desugar to a while loop and an mutable variable holding the iterator.

  • get a &mut reference

    while methods receiving &mut T reference are for the most part conceptually quite similar to methods receiving and returning a T by value, the two things aren't quite the same. (The details have to do with panic safety, that's not too important...) Since mutable references are used a lot in existing Rust code - think of all the &mut self methods out there - and you typically can only create a &mut reference from a local variable that's marked mutable, that's a common reason why you need mutable variables.


mutable variable vs mutable value

its important to note that even a value in an immutable variable (i.e. one introduced with let, not let mut) can be mutated after its moved back out of the variable. If you want to work mostly with immutable variables, but need to call a &mut self method at some place, it does suffice to move the value into a mutable variable just for the method call, and afterwards you can put it back into a (new) immutable variable if you want. E. g. if a type Foo had a method fn mutate(&mut self), you could do

let x = Foo::new(); // immutable variable x
//..... 
// no code here can mutate x
//..... 
let mut x = x; // move into mutable variable 
x.mutate();
let x = x; // move back into an immutable variable
// etc...

or maybe - using a block, and not shadowing anything - you could do

let x = Foo::new(); // immutable variable x
//..... 
// no code here can mutate x
//..... 
let y = {
    let mut local = x; // move into mutable variable 
    local.mutate();
    local // return value of the block, moved into y
};
// y is immutable again
// ... 
// x is still in scope, but it no longer contains a value
// hence it cannot be used anymore

While this game of moving between mutable and immutable variables might seem pointless, there are situations where it can make sense:

Sometimes creating a complex value requires multiple calls to &mut self methods, afterwards you don't intend to mutate the value anymore. In this case adding a let x = x; line to move from a mutable variable to an immutable one can be a useful pattern; it means the reader of the code will not have to worry about missing / overlooking any potential further mutation of x later in the code.

E. g. if I want to build a vector with push calls, I could do

let mut v = Vec::new();
v.push(1);
v.push(2);
let v = v;
/// lots of further code not mutating v

or alternatively

let v = {
    let mut v = Vec::new();
    v.push(1);
    v.push(2);
    v
};
/// lots of further code not mutating v

if I feel like using a block.

6 Likes

I heard a lot about how welcoming the Rust community is before joining this users forum. It happens to be all true.

I couldn't have hoped for a more thorough explanation, thank you.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.