An idea for adding functionality to Rust regarding shadowing

To abstract this a bit, there's a common lang approach we take here that we generally don't want to do changes to what compiles to fix things where it's well-defined what they do but might not be what you want. For example, for x in 0..300 is still legal Rust even if x ends up being u8 and thus that literal wraps. But we do lint it!

So going back down again, if there's something worth doing here, it'd be linting to detect both the known-fine cases that definitely don't need attention and known-troublesome cases that might. That has the big advantage that it doesn't need new MSRVs doesn't need language changes and we can tune exactly what those lints complain about are over time.

Shadowing like let x = x.parse()?; or if let Some(x) = x { is IMHO essential and as a result I'd never agree to totally remove shadowing. But also if people find good heuristics I'd be happy to have some kinds of linting about stuff. The only time I remember getting bit by shadowing was a f32 shadowed by another f32 where I was trying to use the older one. So maybe there's something about "it's weird that you didn't just make that mut" that could be worth warning about, or something. (It'd need lots of analysis to see if that's actually a reasonable rule, but directionally it seems plausible at least.)

Clippy already has a lint for this: shadow_unrelated (along with shadow_reuse and shadow_same which can be used to further restrict shadowing). However, unsurprisingly, it has false-positive cases, a couple of which I found myself:

Something to point out here since I've seen a bit of fast and loose terminology, is that shadowing is not equivalent to reassignment: the original variable continues to exist and has its full lifetime as usual (unless moved from by the shadowing initializer).

This is most obviously important for things like mutex guards, but you may simply need a variable to live just to use a reference to it (apparently lifetime extension either isn't a thing or doesn't always work in Rust?)

The whole point is that once a variable has been shadowed, you can no longer refer to the original one.

Yes? Which is not equivalent to reassignment.

Yes, this creates a new variable, but the old one becomes inaccessible. This concerns me—this pattern resembles a memory leak.

A let binding that shadows an older binding does not drop the original value. That value is inaccessible to the code you write but it will still be dropped when its scope ends, preventing a memory leak.

Another way of saying this is that shadowing is not like reassignment. Shadowing creates a new place (stack variable), and the place where the old value exists is not impacted. While reassignment overwrites a single place.

This reminds me about poor student who only knew about fleas: Fish live in water; they don’t have fur, of course—but if they did, they would certainly have fleas…

Similarly here: it's possible to imagine a different language with linear types instead of affine types… in these languages shadowing would be a memory leak (and thus would be rejected). There are no need for a special syntax either way…

To be honest, this statement gives me the impression that your proposal and our objections have been sufficiently discussed. In principle, we're actually done here.

Yes, that's what the search function in your IDE or editor does for you. That's not the compiler's job.

Who would doubt that? You’ve explained your point of view in such detail by now that it’s tremendously more than clear. What isn’t entirely clear to me is why you’re still getting so worked up about it. Seriously, though. You’ve made your point. We’ve laid out ours. The problem and your proposed solution have been understood, and it’s been explained in detail why that might not be such a good idea. So, in my opinion, that’s the end of the discussion.

No?! A memory leak occurs when memory is declared unnecessarily and is not released. Shadowing does exactly the opposite. Here's a simple example:

let a = 5; //a comes in scope
let a = 10; //previous `let a` statement goes out of scope

A memory leak cannot occur here. The moment a variable goes out of scope, it is deleted, because even the simplest Rust pointers have a self-destruction mechanism. This is the fundamental principle of ownership and borrowing. If you claim that shadowing, as you explain it, causes memory leaks, you haven't understood how Rust works.

Ha! Amusingly of course you can refer to the original:

fn main () {
    let name = "Alice".to_string();
    let name_ref = &name;
    let name = "Bob".to_string();
    println!("Hello {name_ref} and {name}");
}

I know that's probably not what you meant. But it does highlight the shadowed thing does not disappear.