Rust let is a point of failure

Today, I encountered a problem when writing code. An encapsulated function processes logic, but the function is cyclic outside, and there is a let mut hashmap definition inside the function. This causes the hashmap to be reinitialized every time it is looped, causing deviations from expectations. As a result, I think that let should not allow variable shadowing. For example, go's var, Typescript/JavaScript's let, and const all do not allow variable shadowing. Why is rust like this?

Can you give a code example of the issue you encountered? It's hard to tell by just your description.

Unityped dynamic languages are completely different when it comes to how scoping works, so that comparison is not useful.

Rust has a compiler to give you "hey, you never edited that outer one" warnings, and you can't do things like x = x.to_string(); in rust -- the way you can in JS that doesn't care -- so shadowing is useful in Rust in a way it isn't in JS.

9 Likes

Clippy has lints for shadowing, the most useful one being the clippy::shadow_unrelated lint, which prevents accidental shadowing, while allowing idiomatic shadowing patterns.

5 Likes

Please remove the "Unsafe Code Guidelines" tag from this thread, it has nothing to do with the UCG.

2 Likes

Then pass the hash map as a parameter instead of constructing it in the function?

That's a common pattern, std does it for Strings and Vecs.

I'm afraid that ship has sailed. Changing these kinds of rules retroactively means breaking real, extant code, be in on github somewhere or in proprietary code. That in turn would conflict with Rust's stability guarantees.

5 Likes

In Rust, existence of variables is semantically meaningful.

In JS code like this:

let string = example.toLowerCase().trim();

is always the same as:

let tmp = example.toLowerCase();
let string = tmp.trim();

but in Rust, these two examples mean different things.

let string = example.to_lowercase().trim(); // ERROR!!!
let string = example.to_lowercase();
let string = string.trim(); // OK

In Rust you may need to break an expression into multiple parts, to ensure the temporary intermediate results are kept.

JS is dynamically typed, and doesn't have borrowed vs owned distinction, so if you want, you can reuse the same variable for multiple steps of a computation:

let string = example.toLowerCase();
string = string.trim();

but in Rust it's not always possible when the types change:

let mut string = example.to_lowercase();
string = string.trim(); // ERROR!!!

which makes shadowing useful, because you can shadow a variable with the same name but a different type, which is almost as convenient as dynamically typed variables. And you don't have to create string1, string2, string3, and keep track of which one is the latest one.

6 Likes

Is this code unidiomatic? I took it straight from the docs.

for entry in fs::read_dir(dir)? {
    let entry = entry?;
    // ...
}

... What? Yes they do, they just require a block in between, and a loop counts as a block - from what I could understand of the code you're describing this would be identical in JavaScript (which is rare to be able to say in Rust!).

2 Likes