我对 Rust 中的“移动语义”以及所有权转移时数据是否被复制感到好奇。这是一些演示代码:
#[derive(Debug)]
struct Foo {
name: i32,
age: i32,
}
fn main() {
let foo = Foo { name: 111, age: 1 };
let ptr_foo = std::ptr::addr_of!(foo);
println!("Address of foo: {:p}", ptr_foo);
let bar = foo;
let ptr_bar = std::ptr::addr_of!(bar);
println!("Address of bar: {:p}", ptr_bar);
}
我最初以为“有一个变量 foo,移动后,我们简单地将其重命名为 bar”,从而避免了复制其对应数据的需要。
为了进一步调查,我使用了 VSCode 中的调试功能以及名为“Hex Editor”的插件,发现
foo
和 bar
(内存地址 0x7fffffffd7d8
和 0x7fffffffd828
)包含相同的数据 (6F 00 00 00 01 00 00 00
) ).
这是否表明 Rust 即使在
move
期间实际上也会执行复制操作,例如在本例中复制结构?此行为是否会根据是否处于发布模式而有所不同?
“移动”在许多语言(不仅仅是 Rust)中是一个语义术语,指的是“所有权转移”,并且并不“必然”规定该转移的机制。 在 Rust 中,移动值会将数据复制到其他地方,但会使值的源不可用(除非该源稍后使用有效值重新初始化,或者被移动的类型实现了 Copy
)。
如何在 Rust 中实现这一点是通过从源值到目标的按位复制。 在更高的优化级别,可以删除此副本,但也可能不会。
请注意,复制仅扩展到直接包含在正在移动的值中的值。 例如,当移动 Vec
时,它会复制
Vec
的数据指针、长度和容量,但不复制
Vec
的实际元素,这些元素位于该指针后面。 这允许您以相同的成本移动 any
Vec
- 移动巨大的 Vec
和空的 Vec
都需要复制完全相同数量的内容(3 个指针大小的值)。在您的代码中,获取 foo
和 bar
的地址可能会阻止优化器实际删除副本,因为它无法统一它们的内存位置。 这样做并为两个变量返回相同的地址将违反 as-if 规则。
换句话说: 如果您尝试查看代码将如何编译,不要更改该代码以检查代码内部的值
,否则您几乎肯定会以某种方式更改优化器的行为。 相反,请查看生成的机器代码。