At the risk of sounding preachy, I would strongly reiterate @DanielKeep's first point; the fact that rust allows shadowing is clearly no accident, and it is a feature found by many to be extremely desirable in modern languages. Whether it is a danger or a boon largely comes down to coding style, and as long as most variables are declared close to where they are used, you have a fair bit to gain and little to worry about.
The most obvious place where it is useful is in closures; the majority of closures are simple 1-liners that shouldn't have to worry about context, and there is little reason to call most closure variables anything but x
. By using such a name you are making a statement: this variable is really short-lived and you should have no trouble seeing how it is used.
// option 1
test_results.filter(|x| x.iter().all(|&&x| x))
// option 1b
test_results.filter(|t| t.iter().all(|&&c| c))
// option 2
test_results.filter(|test| test.iter().all(|&&case| case))
When I look at #1 I don't "see" variables named x; I focus on the shape and method names. When I look at #2 my eyes must quickly scan ahead to see whether test
is used a second time. So while #2 has its merits, in this respect, I think it is also noisy.
In such a short closure, it is very difficult to accidentally shadow something without noticing, as it is often the difference between |x| center + x
and |x| x + x
.
Notice that rust even permits shadowing within the same scope, i.e. rebinding.
// without shadowing
let iter = vec.into_iter();
let modified = modify(iter);
let doubled = double(modified);
for x in modified { ... }
//-----------
// with shadowing
let iter = vec.into_iter();
let iter = modify(iter);
let iter = double(iter);
for x in iter { ... }
In the second version, you can easily delete one of the function calls or insert another when the logic needs to be revised.
(edit: the for x in modified
was both entirely unintentional and yet quite exemplary!)
Caveat: There is one real danger of shadowing, which is when you rename a variable in an outer scope; this is a circumstance where e.g. |x| y + x
can turn into |x| x + x
without your immediate knowledge. I would exercise a bit of caution using find and replace, and hope that any more sophisticated tooling for refactoring would catch this.