Ownership of shadowing

I know this question may be silly, but seems the rust book does not cover this point.

As we know, below code does not compile because s1 is no longer available, the ownership of s1 was moved to s2.

let s1 = String::from("One");
let s2 = s1; // s1 moved here
println!("{}", s1);

Now let's see this code:

let s1 = String::from("One");
let s1 = String::from("Two"); // what happened to the former `s1`?
println!("{}", s1); 

This code will compile and print Two, but I would like to know, what happened to the former s1?
Is it just go out of scope and dropped? Will the memory be freed?

1 Like

The original s1 variable still exists, you just can't access it any more once it's been shadowed. It will be dropped at the end of the scope, I believe.

2 Likes

...unless you have created references to it before shadowing, of course:

fn main() {
    let s1 = String::from("One");
    let s1_ref = &s1;
    let s1 = String::from("Two");
    println!("{} {}", s1_ref, s1); // prints "One Two"
}

Playground

1 Like

Good point - it'd have been more accurate to say "you can't access it via that name any more".

This looks kind of awful...what if I forget I already have one reference to s1 and assuming s1_ref should be "Two"?

You can create a custom type which prints to stdout when dropped and see what you get.

struct DropTest(i32);

impl Drop for DropTest {
    fn drop(&mut self) {
        println!("dropping no {}", self.0)
    }
}

fn main() {
    let t1 = DropTest(1);
    let t1 = DropTest(2);
    println!("Hello, world!");
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=cd04457707b9cf869526578da9a99662

1 Like
let s1 = X;
let s2 = Y;

are exactly the same as:

let s1 = X;
let s1 = Y;

It's still two variables, each holding its own value, having its own scope, and own drop, etc. The way to think about it is that name of the variable totally doesn't matter. Rust tracks variables in its own way, separate from their name.

2 Likes

Indeed, but that's not what the presented example was doing of course.

Then you've written yourself a bug :slight_smile:

1 Like

I have not go to this part yet, seems that you write a own drop function and overridden the default drop?

I only have limited experience about Java's System.gc(), and it's not guaranteed to run each time when a variable goes out of scope, so drop will always be called in Rust?

1 Like

Yes, values are dropped immediately when they go out of scope, unless they are passed to std::mem::forget or wrapped in ManuallyDrop.

1 Like

To add to @Cerber-Ursi's comment: Drop in Rust is more like the destructor in C++. It will always run at the exact point in your code where the object goes out of scope, except when you override it as @Cerber-Ursi pointed out.

This enables the code to be baked into your program at a known location at compile time instead of having to pause to let a GC run to figure out what can be freed, but it makes things a little more complicated around reference cycles and self-references.

2 Likes

Where does Box::leak fit into this given that it too effectively prevents the memory from being released until the end of the application?

It uses ManuallyDrop - btw, you can click on the [src] link in the documentation to see this:

pub fn leak<'a>(b: Self) -> &'a mut T
    where
        A: 'a,
    {
        unsafe { &mut *mem::ManuallyDrop::new(b).0.as_ptr() }
    }
1 Like

Applying what is described in Rustonomicon, the pseudo de-sugared version of the example code might look like the following:

'a: {
     let s1: String = String::from("One");
     'b: { 
          let s1: String = String::from("Two");
          'c: { // besides the main point, but new scope to where the ownership is passed
               println!("{}", s1); 
          }
     }
}

Putting two and two together, I suspect

... is effected using the different scope ids the compiler uses to enforce many of our beloved rules. So, the two s1 are distinct because they live in different scopes. This is more often going to be the case because with every let statement Rust creates a new scope (so, tracking variables effectively becomes tracking scope ids).

One benefit of the overwrite is to enforce an intent not to extend the lifetime of the first scope by inadvertently referencing that 'a:s1 memory again.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.