简体   繁体   中英

Variable shadowing in the same scope in Rust?

fn main() {
    let x = 5;
    println!("{}", x);

    let x = 3.14;
    println!("{}", x);

    let x = "Hello";
    println!("{}", x);
}
  1. What happens to the previous values? My understanding is that they are not destroyed.

  2. Is there a way to still access these values?

  3. If they are still consuming memory space, is it possible to release that space?

  1. What happens to the previous values?

Nothing.

  1. Is there a way to still access these values?

No.

  1. If they are still consuming memory space, is it possible to release that space?

No.


Now, all of the above should be true from the perspective of the code. They're not necessarily true from the perspective of the optimiser . For example, the optimiser might notice that uses of the first and second x 's don't overlap, so it can re-use the stack storage of the first x for the second x .

Or it might not. The language itself, insofar as I am aware, has no opinion on the subject.

The only way I'm aware of that you can guarantee that a local variable will definitely release it's stack storage is to return from the function it's defined in.

... well, until you consider inlining , which could also make that not true.

Short version: don't worry about it, unless you're using so much stack space that it's causing actual, measurable problems.

The value can be moved out before the variable is shadowed, but ultimately the value can't be accessed from a shadowed variable.

Answer to Q1: The decision is up to the compiler and data type and size and OS and system memory load (usually stack based data types stay to the end of the main , and heap based data types with big memory footprint may need to drop if needed).
Answer to Q2: After shadowing: No , before shadowing: Yes ( Values ), run this code .
Answer to Q3: After shadowing: No , before shadowing: see: Disposes of a value . and Rust manual memory management


Variable scoping and shadowing:
Advantages:
1. Since data cannot be accessed from outer scope, Data Integrity is preserved.
2. When "we need more alphabets", this is nice way to limit variables scope. Also this works well when you need more local variables or scope.


A way to still access these values before shadowing (note: move forced the closure to take ownership of 'x'):

use std::{thread, time};
fn main() {
    let mut v = vec![];
    let d = time::Duration::from_millis(100);

    let x = 5;
    println!("{}", x);
    v.push(thread::spawn(move || {
        for _ in 1..10 {
            thread::sleep(d);
            println!("Thread 1: {}", x);
        }
    }));

    let x = 3.14;
    println!("{}", x);
    v.push(thread::spawn(move || {
        for _ in 1..10 {
            thread::sleep(d);
            println!("Thread 2: {}", x);
        }
    }));

    let x = "Hello";
    println!("{}", x);
    v.push(thread::spawn(move || {
        for _ in 1..10 {
            thread::sleep(d);
            println!("Thread 3: {}", x);
        }
    }));

    for child in v {
        let _ = child.join();
    }
}

output:

5
3.14
Hello
Thread 1: 5
Thread 3: Hello
Thread 2: 3.14
Thread 2: 3.14
Thread 3: Hello
Thread 1: 5
Thread 1: 5
Thread 3: Hello
Thread 2: 3.14
Thread 2: 3.14
Thread 3: Hello
Thread 1: 5
Thread 2: 3.14
Thread 3: Hello
Thread 1: 5
Thread 2: 3.14
Thread 1: 5
Thread 3: Hello
Thread 2: 3.14
Thread 1: 5
Thread 3: Hello
Thread 3: Hello
Thread 2: 3.14
Thread 1: 5
Thread 1: 5
Thread 2: 3.14
Thread 3: Hello

Note: move forces the closure to take ownership of 'x', so the address of local x is not the same as thread x but the value is:

use std::thread;
fn main() {
    let mut v = vec![];

    let x = 5;
    println!("{:p}", &x);
    v.push(thread::spawn(move || {
        println!("Thread 1: {:p}", &x);
    }));

    let x = 3.14;
    println!("{:p}", &x);
    v.push(thread::spawn(move || {
        println!("Thread 2: {:p}", &x);
    }));

    let x = "Hello";
    println!("{:p}", &x);
    v.push(thread::spawn(move || {
        println!("Thread 3: {:p}", &x);
    }));

    for child in v {
        let _ = child.join();
    }
}

output:

0x8bf934
0x8bf9b8
0x8bfa40
Thread 1: 0x4a3faec
Thread 2: 0x4c3fae8
Thread 3: 0x4e3fa70

As far as I know there is only one thing to keep in mind with shadowing: when values are heap allocated.

From the book :

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

the previous value is not more accessible after shadowing and it will be destroyed at the end of the scope, not when the variable is shadowed.

If value are on the stack there is nothing to worry: stack memory management is completely in the hands of the processor.

Instead, if the value is heap allocated, shadowing can be seen as a temporary memory leak that will be released at the end of the scope.

If this may be an issue we can explicitly free the memory with drop() before shadowing:

struct Foo {
    _v: Vec<i32>
}

impl Drop for Foo {
    fn drop(&mut self) {
        println!("dropping foo");
    }
}


fn main() {
    println!("start");

    let x = Foo {_v: vec![1,2,3]};

    drop(x);

    let x = 100;

    println!("end");

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM