当 Rust 中一个值覆盖另一个值时,堆栈上会发生什么?
What happens on the stack when one value shadows another in Rust?
我正在阅读Mastering Rust。第一章末尾有一个练习,其中提供了示例代码,任务是修复它,使用通常非常有用的编译器错误消息进行迭代。
我预计以下是错误但不是:
for line in reader.lines() {
let line = line.expect("Could not read line.");
对于完整的上下文,我有 entire code in a gist。这是我修复后的代码,相关行是 37 和 38。但是它需要提供一个文本文件作为参数。
我期待一个错误,因为 line
在堆栈上(至少指针在)。难道还可以销毁替换毫无怨言吗?
在内存管理和堆栈方面发生了什么?我假设 line
实际上是对字符串(&str
类型)的引用。那么,这很好,因为在任何一种情况下,指针本身——堆栈上的对象——只是一个 usize
,因此两个 line
对象在堆栈上的大小相同。
我可以用不同尺寸的东西做这个吗?第二行可以说:
let line: f64 = 3.42;
在这种情况下,对象本身在堆栈上,并且可能大于 usize
。
只要用 let
声明一个变量,它就是一个全新的变量,与它之前的任何变量都不同。即使已经存在同名变量,当新变量在范围内时,原始变量为 shadowed。如果一个变量被隐藏,它通常是不可访问的。
在新变量超出范围后旧变量仍在范围内的情况下,或者如果旧变量具有 Drop
实现,则可以访问旧变量的值。
我们可以在下面的示例中看到这一点。
#[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.
}
这并不是说原始变量就一定会存在。编译器优化可能会导致原始变量被覆盖,尤其是当原始变量不再被访问时。
密码
pub fn add_three(x: u32, y: u32, z: u32) -> u32 {
let x = x + y;
let x = x + z;
x
}
example::add_three:
lea eax, [rdi + rsi]
add eax, edx
ret
如果你像我一样对汇编代码不太熟悉,这基本上是
- 将 x 和 y 相加并将结果放入一个变量中(称之为 w)。
- 将 z 添加到 w 并用结果覆盖 w。
- Returnsw.
所以(除了输入参数),即使我们使用了两次 let x = ...
,也只使用了一个变量。中间结果 let x = x + y;
被覆盖。
我正在阅读Mastering Rust。第一章末尾有一个练习,其中提供了示例代码,任务是修复它,使用通常非常有用的编译器错误消息进行迭代。
我预计以下是错误但不是:
for line in reader.lines() {
let line = line.expect("Could not read line.");
对于完整的上下文,我有 entire code in a gist。这是我修复后的代码,相关行是 37 和 38。但是它需要提供一个文本文件作为参数。
我期待一个错误,因为 line
在堆栈上(至少指针在)。难道还可以销毁替换毫无怨言吗?
在内存管理和堆栈方面发生了什么?我假设 line
实际上是对字符串(&str
类型)的引用。那么,这很好,因为在任何一种情况下,指针本身——堆栈上的对象——只是一个 usize
,因此两个 line
对象在堆栈上的大小相同。
我可以用不同尺寸的东西做这个吗?第二行可以说:
let line: f64 = 3.42;
在这种情况下,对象本身在堆栈上,并且可能大于 usize
。
只要用 let
声明一个变量,它就是一个全新的变量,与它之前的任何变量都不同。即使已经存在同名变量,当新变量在范围内时,原始变量为 shadowed。如果一个变量被隐藏,它通常是不可访问的。
在新变量超出范围后旧变量仍在范围内的情况下,或者如果旧变量具有 Drop
实现,则可以访问旧变量的值。
我们可以在下面的示例中看到这一点。
#[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.
}
这并不是说原始变量就一定会存在。编译器优化可能会导致原始变量被覆盖,尤其是当原始变量不再被访问时。
密码
pub fn add_three(x: u32, y: u32, z: u32) -> u32 {
let x = x + y;
let x = x + z;
x
}
example::add_three:
lea eax, [rdi + rsi]
add eax, edx
ret
如果你像我一样对汇编代码不太熟悉,这基本上是
- 将 x 和 y 相加并将结果放入一个变量中(称之为 w)。
- 将 z 添加到 w 并用结果覆盖 w。
- Returnsw.
所以(除了输入参数),即使我们使用了两次 let x = ...
,也只使用了一个变量。中间结果 let x = x + y;
被覆盖。