问题
I am reading Mastering Rust. There is an exercise at the end of the first chapter where sample code is provided, and the task is to fix it, iterating by using the generally quite helpful compiler error messages.
I was expecting that the following was an error but it is not:
for line in reader.lines() {
let line = line.expect("Could not read line.");
For complete context, I have the entire code in a gist. That's the code after I fixed things, and the relevant rows are 37 & 38. However it requires feeding a text file as an argument.
I was expecting an error because line
is on the stack (at least the pointer is). Is it right that it can still be destroyed and replaced with no complaint?
What happens under the hood regarding memory management and the stack? I presume that line
is actually a reference to a string (a &str
type). So, then, this is fine because in either case, the pointer itself - the object on the stack - is just a usize
, so that both line
objects are of the same size on the stack.
Can I do this with something of a different size? Could the second line have said:
let line: f64 = 3.42;
In this case, the object itself is on the stack, and it is potentially larger than usize
.
回答1:
Whenever a variable is declared with let
, it's an entirely new variable, separate from anything before it. Even if a variable with the same name already exists, the original variable is shadowed while the new variable is in scope. If a variable is shadowed, it's normally inaccessible.
It's possible to access the value of the old variable in situations where the old variable is still in scope after the new variable falls out of scope, or if the old variable has a Drop
implementation.
We can see this in action in the following example.
#[derive(Debug)]
struct DroppedU32(u32);
impl Drop for DroppedU32 {
fn drop(&mut self) {
eprintln!("Dropping u32: {}", self.0);
}
}
fn main() {
let x = 5;
dbg!(&x); // the original value
{
let x = 7;
dbg!(&x); // the new value
}
dbg!(&x); // the original value again
let y = DroppedU32(5);
dbg!(&y); // the original value
let y = DroppedU32(7);
dbg!(&y); // the new value
// down here, when the variables are dropped in
// reverse order of declaration,
// the original value is accessed again in the `Drop` impl.
}
(playground)
That's not to say that the original variable is guaranteed to still exist. Compiler optimizations can cause the original variable to be overwritten, especially if the original variable isn't accessed again.
The code
pub fn add_three(x: u32, y: u32, z: u32) -> u32 {
let x = x + y;
let x = x + z;
x
}
compiles to
example::add_three:
lea eax, [rdi + rsi]
add eax, edx
ret
If you're like me and aren't too familiar with assembler code, this basically
- Adds x and y and puts the result in a variable (call it w).
- Adds z to w and overwrites w with the result.
- Returns w.
So (besides the input parameters), there's only one variable used, even though we used let x = ...
twice. The intermediate result let x = x + y;
gets overwritten.
来源:https://stackoverflow.com/questions/56370397/what-happens-on-the-stack-when-one-value-shadows-another-in-rust