"let x" two times. Why is it allowed?

why is below code valid and allowed in Rust.

let x = 5;
println!("Hello, world!. I am {x}");
let x = 6;
println!("Hello, world!. I am {x}");

“Shadowing” in “The Rust Programming Language”, chapter 3

1 Like

The trivial answer for any "why is it allowed" is "why shouldn't it be?", of course.

Rust has a few tricks that rely on shadowing (implementing pin!, for example), but really the answer is that it's just not really anywhere as big a problem as it would be in other languages due to the much stricter type system and borrow checker, and it's simpler to just use the same name sometimes.

3 Likes

Sure. Thank you very much @steffahn and @simonbuchan for the quick answers. Its a great help.

If you are not a fan of shadowing (as I am not), you can turn it off using Clippy.

2 Likes

I too was surprised to find that Rust allows shadowing. However it turns out not to me a problem in practice due to Rust's strict type checking. It also turns out to be very convenient. You can grab some data in some format, say reading a bunch of JSON, and give it a name. Then you can parse it into a structure, with the same name. Which is nice because why shouldn't what is effectively the same data have the same name? And it saves having to make up names for such temporaries, like "myDataJson" then "myData".

My feeling is that if your functions and methods are so long and have so many temporary values floating around that shadowing causes confusion then the problem is not that shadowing will confuse things but that your functions are two long.

3 Likes

For the same type like that it's less commonly a good thing, though in test code it can still be quite handy.

It's most useful when you give something the same name, but a different type, where it should have the same name because it's conceptually the same thing.

For example, take a function like

fn foo(x: u32) {
    let Some(x) = NonZeroU32::new(x)
    else { panic!("x must not be zero"); };
    … rest of the method …
}

There the new binding hiding the other one is actually a good thing, since it keeps you from using the wrong one, and avoids needing to pick a new name.

1 Like

Note, too, that for the sorts of cases you're talking about, you can sometimes reduce shadowing by using the value of a block as the initial value of a binding.

So, instead of:

let mut my_data = Vec::new();
input.read_to_end(&mut my_data)?;
let my_data: MyStruct = serde_json::from_slice(&my_data)?;

which works, but has shadowing, you can write:

let my_data: MyStruct = {
    let mut raw_bytes = Vec::new();
    input.read_to_end(&mut raw_bytes)?;
    serde_json::from_slice(&my_data)?
};

This is not always clearer, but does remove the shadowing, replacing it with a block instead.

2 Likes