29

I saw in the Rust book that you can define two different variables with the same name:

let hello = "Hello";
let hello = "Goodbye";

println!("My variable hello contains: {}", hello);

This prints out:

My variable hello contains: Goodbye

What happens with the first hello? Does it get freed up? How could I access it?

I know it would be bad to name two variables the same, but if this happens by accident because I declare it 100 lines below it could be a real pain.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Alexander Luna
  • 5,261
  • 4
  • 30
  • 36
  • 6
    1. Rust doesn't even have a GC. 2. That should be explained in the Book on [Shadowing](https://doc.rust-lang.org/book/second-edition/ch03-01-variables-and-mutability.html#shadowing) – E_net4 Jan 12 '18 at 13:40
  • 1
    I know Rust doesn't have GC but the fact that is cleans after itself without you telling it is in some way a GC. Is there a way of cleaning memory manually ? Or does the memory only get freed up after it falls out of scope as Shepmaster mentioned ? – Alexander Luna Jan 12 '18 at 13:52
  • That is not quite what you are asking in the question. – E_net4 Jan 12 '18 at 13:54
  • 2
    I don't understand why this question is being down-voted. The Rust Book doesn't explain at all what happens with the memory of shadowed variables. The answer from @Shepmaster explains it really well! – Logan Reed Jan 12 '18 at 22:08
  • 2
    @LoganReed if you check the [revision history](https://stackoverflow.com/posts/48227347/revisions), you'll see that OP originally asked if Rust's "garbage collector" did X or Y. The downvote arrow tooltip lists "this question does not show any research effort" as a reason to use it. Cursory searching would show that Rust does not have a GC, so my guess is that people downvoted for some combination of those reasons. You are encouraged to upvote if you think the question in its current form is good. – Shepmaster Jan 12 '18 at 22:12

3 Answers3

39

Rust does not have a garbage collector.

Does Rust free up the memory of overwritten variables?

Yes, otherwise it'd be a memory leak, which would be a pretty terrible design decision. The memory is freed when the variable is reassigned:

struct Noisy;
impl Drop for Noisy {
    fn drop(&mut self) {
        eprintln!("Dropped")
    }
}

fn main() {
    eprintln!("0");
    let mut thing = Noisy;
    eprintln!("1");
    thing = Noisy;
    eprintln!("2");
}
0
1
Dropped
2
Dropped

what happens with the first hello

It is shadowed.

Nothing "special" happens to the data referenced by the variable, other than the fact that you can no longer access it. It is still dropped when the variable goes out of scope:

struct Noisy;
impl Drop for Noisy {
    fn drop(&mut self) {
        eprintln!("Dropped")
    }
}

fn main() {
    eprintln!("0");
    let thing = Noisy;
    eprintln!("1");
    let thing = Noisy;
    eprintln!("2");
}
0
1
2
Dropped
Dropped

See also:

I know it would be bad to name two variables the same

It's not "bad", it's a design decision. I would say that using shadowing like this is a bad idea:

let x = "Anna";
println!("User's name is {}", x);
let x = 42;
println!("The tax rate is {}", x);

Using shadowing like this is reasonable to me:

let name = String::from("  Vivian ");
let name = name.trim();
println!("User's name is {}", name);

See also:

but if this happens by accident because I declare it 100 lines below it could be a real pain.

Don't have functions that are so big that you "accidentally" do something. That's applicable in any programming language.

Is there a way of cleaning memory manually?

You can call drop:

eprintln!("0");
let thing = Noisy;
drop(thing);
eprintln!("1");
let thing = Noisy;
eprintln!("2");
0
Dropped
1
2
Dropped

However, as oli_obk - ker points out, the stack memory taken by the variable will not be freed until the function exits, only the resources taken by the variable.

All discussions of drop require showing its (very complicated) implementation:

fn drop<T>(_: T) {}

What if I declare the variable in a global scope outside of the other functions?

Global variables are never freed, if you can even create them to start with.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • I know Rust doesn't have GC but the fact that is cleans after itself without you telling it is in some way a GC. Is there a way of cleaning memory manually ? What if I declare the variable in a global scope outside of the other functions ? – Alexander Luna Jan 12 '18 at 13:46
  • Ok, when it falls out of scope it will free up the memory again. Do you know if there is a manual way of doing it or does Rust decide that alone ? – Alexander Luna Jan 12 '18 at 13:57
  • 9
    You have a fundamental problem with freeing memory: Stacks can (by definition) only be modified at the top (or bottom, depending on your view). So you can't "free" memory in the middle. If you have 3 variables `a`, `b` and `c` in that order, you can't "free" `b`, you can just stop using it or reuse its memory, but not free it. – oli_obk Jan 12 '18 at 14:03
16

There is a difference between shadowing and reassigning (overwriting) a variable when it comes to drop order.

All local variables are normally dropped when they go out of scope, in reverse order of declaration (see The Rust Programming Language's chapter on Drop). This includes shadowed variables. It's easy to check this by wrapping the value in a simple wrapper struct that prints something when it (the wrapper) is dropped (just before the value itself is dropped):

use std::fmt::Debug;

struct NoisyDrop<T: Debug>(T);

impl<T: Debug> Drop for NoisyDrop<T> {
    fn drop(&mut self) {
        println!("dropping {:?}", self.0);
    }
}

fn main() {
    let hello = NoisyDrop("Hello");
    let hello = NoisyDrop("Goodbye");

    println!("My variable hello contains: {}", hello.0);
}

prints the following (playground):

My variable hello contains: Goodbye
dropping "Goodbye"
dropping "Hello"

That's because a new let binding in a scope does not overwrite the previous binding, so it's just as if you had written

    let hello1 = NoisyDrop("Hello");
    let hello2 = NoisyDrop("Goodbye");

    println!("My variable hello contains: {}", hello2.0);

Notice that this behavior is different from the following, superficially very similar, code (playground):

fn main() {
    let mut hello = NoisyDrop("Hello");
    hello = NoisyDrop("Goodbye");

    println!("My variable hello contains: {}", hello.0);
}

which not only drops them in the opposite order, but drops the first value before printing the message! That's because when you assign to a variable (instead of shadowing it with a new one), the original value gets dropped first, before the new value is moved in.

I began by saying that local variables are "normally" dropped when they go out of scope. Because you can move values into and out of variables, the analysis of figuring out when variables need to be dropped can sometimes not be done until runtime. In such cases, the compiler actually inserts code to track "liveness" and drop those values when necessary, so you can't accidentally cause leaks by overwriting a value. (However, it's still possible to safely leak memory by calling mem::forget, or by creating an Rc-cycle with internal mutability.)

See also

trent
  • 25,033
  • 7
  • 51
  • 90
1

There are a few things to note here:

In the program you gave, when compiling it, the "Hello" string does not appear in the binary. This might be a compiler optimization because the first value is not used.

fn main(){
  let hello = "Hello xxxxxxxxxxxxxxxx"; // Added for searching more easily.
  let hello = "Goodbye";

  println!("My variable hello contains: {}", hello);
}

Then test:

$ rustc  ./stackoverflow.rs

$ cat stackoverflow | grep "xxx"
# No results

$ cat stackoverflow | grep "Goodbye"
Binary file (standard input) matches

$ cat stackoverflow | grep "My variable hello contains"
Binary file (standard input) matches

Note that if you print the first value, the string does appear in the binary though, so this proves that this is a compiler optimization to not store unused values.

Another thing to consider is that both values assigned to hello (i.e. "Hello" and "Goodbye") have a &str type. This is a pointer to a string stored statically in the binary after compiling. An example of a dynamically generated string would be when you generate a hash from some data, like MD5 or SHA algorithms (the resulting string does not exist statically in the binary).

fn main(){
  // Added the type to make it more clear.
  let hello: &str = "Hello";
  let hello: &str = "Goodbye";

  // This is wrong (does not compile):
  // let hello: String = "Goodbye";

  println!("My variable hello contains: {}", hello);
}

This means that the variable is simply pointing to a location in the static memory. No memory gets allocated during runtime, nor gets freed. Even if the optimization mentioned above didn't exist (i.e. omit unused strings), only the memory address location pointed by hello would change, but the memory is still used by static strings.

The story would be different for a String type, and for that refer to the other answers.

Chris Vilches
  • 986
  • 2
  • 10
  • 25