Newbie question: memory leaks by shadowing?

Hi guys,

I am giving rust a try as a possible replacement for C++. I am reading through "The Book" (which is BTW excellent) and got surprised by variable binding shadowing.

Do I understand it right that it is possible to leak memory by shadowing the variable in the same scope? I mean in principle - I understand that the issue is easily solvable by clippy if need be.

https://doc.rust-lang.org/book/variable-bindings.html

Note that shadowing a name does not alter or destroy the value it was bound to, and the value will continue to exist until it goes out of scope, even if it is no longer accessible by any means.

Like this:

let mut guess = String::new();
let mut guess = String::new();
// Is the first instance not accessible anymore but still in memory?

The point about "does not alter or destroy the value" is referring only to the immediate act of shadowing. You can't access the shadowed variable by name anymore, but it will still be destroyed at the end of its scope as normal.

4 Likes

Sorry,I just realized I tried to imagine the worst possible case and made some assumptions along the way.

So for "long lived scope and resource handle" (abstract data type allocating memory on heap) it is "leaked" in the sense that there's no way to access the shadowed instance and free the memory. Is it so?

I am totally new to rust and have no idea about dynamic memory handling yet. Maybe I just need to read on but my impatience and curiosity got the best of me.

Yes, it will hang around until the end of that long-lived scope. You can use drop before shadowing if you know this is an issue, or if possible just try to reduce the scope of that variable.

Also, note that shadowing is only relevant to this in that you can't name the shadowed variable later. But even if you had given everything unique names, they'd still have the exact same destruction behavior.

Ok, thanks. It isn't necessarily a big deal. I just wanted to understand all implications of (very) hypothetical bug.

For the historical record though right now in my current state of knowledge it seems like point for C++ and One Definition Rule.

C++ does let you shadow names in nested scopes though. You can think of Rust as having an implicit nested scope starting at every let.

Excuse my ignorance (and novice-ness) - but I felt the same way when I came across Shadowing in The Book. It felt as though it went against Rust's safety and efficiency rules.

Is there any particular use-cases where Shadowing would be wanted in a systems language?

I can see this being a problem in game development if Shadowing was used in the main game loop.

Shadowing is great in a systems language, because it means you can often write code without mutation, and just shadow variables as you process data. Less mutation = less things to worry about.

Also, even if you were to "leak" memory in a loop, you can only leak a fixed number of values.

2 Likes

Why would it be a problem for a game loop? The back edge of a loop is the end of a scope, so it's not like you're going to accumulate memory each iteration.

2 Likes

Sorry, this was where my confusion was. I didn't know that the memory would drop after each iteration (I'm a bit new to this stuff). That makes perfect sense then.

One common example of where shadowing is useful is to remove boilerplate like this (explicit types in the bindings just for demo purposes):

let xStr: &str = ...// get a &str representation of `Foo` from somewhere
let x: Foo = ...// construct a `Foo` from xStr, only care about `x` in the remainder of the function

Of course, you can handle the above via other means, but the shadowing solves that without much fuss:

let x = ... // as above
let x = ... // as above

This is even more pronounced when dealing with RAII guards or constructions that borrow the original; then the "leak" is clearly quite desirable!

let stdout = io::stdout();
let mut stdout = stdout.lock();
// use stdout...

The way you can think about it: it is not shadowed from the compiler perspective.

Very roughly it's like this in C:

int main() {
    int a;
#define a a2
    int a;
}

So you still have two distinct things, and Rust will free them as appropriate regardless of what is the name of the variable.

BTW, shadowing is very useful to extend life of temporary expressions or to convert them.

fn any_path_like<P: AsRef<Path>>(path: P) {   // takes String, PathBuf, etc.
   let path = path.as_ref(); // but needs `as_ref()` called to get an actual Path reference
}

so I can use a single name for both variables, and don't have to come up with two names like path_arg & path_ref or such.

4 Likes