移动语义
An assignment will transfer ownership between variables:
fn main() { let s1: String = String::from("Hello!"); let s2: String = s1; println!("s2: {s2}"); // println!("s1: {s1}"); }
- 将
s1
赋值给s2
,即转移了所有权。 - When
s1
goes out of scope, nothing happens: it does not own anything. - 当
s2
离开作用域时,字符串数据被释放。
移动到 s2
中之前:
移动到 s2
中之后:
你将值传递给函数时,该值会被赋给函数 参数。这就转移了所有权:
fn say_hello(name: String) { println!("Hello {name}") } fn main() { let name = String::from("Alice"); say_hello(name); // say_hello(name); }
This slide should take about 5 minutes.
-
指出这与 C++ 中的默认值相反。除非你使用
std::move
(并已定义 move 构造函数!),否则 C++ 中的默认值是按值复制的。 -
只有所有权发生了转移。是否会生成任何机器码来操控数据本身是一个优化方面的问题,系统会主动优化此类副本。
-
简单的值(例如整数)可以标记为“Copy”(请看后续幻灯片)。
-
在 Rust 中,克隆是显式的(通过使用
clone
)。
在 say_hello
示例中:
- 首次调用
say_hello
时,main
便放弃了name
的所有权。此后,main
中不能再使用name
。 - 在
say_hello
函数结束时,系统会释放为name
分配的堆内存。 - 如果
main
将name
作为引用 (&name
) 传递过去,且say_hello
接受作为参数的引用,则可保留所有权。 - 此外,
main
也可以在首次调用时传递name
的克隆 (name.clone()
)。 - 相较于 C++,Rust 通过将移动语义设为默认值,并强制程序员进行显式克隆,更难以无意中创建副本。
探索更多
Defensive Copies in Modern C++
现代 C++ 以不同的方式解决此问题:
std::string s1 = "Cpp";
std::string s2 = s1; // Duplicate the data in s1.
s1
中的堆数据被复制,s2
获得自己的独立副本。- 当
s1
和s2
离开作用域时,它们会各自释放自己的内存。
复制-赋值之前:
复制-赋值之后:
关键点:
-
C++ 做出了与 Rust 略有不同的选择。由于“=”会复制数据,因此必须克隆字符串数据。否则,当任一字符串超出范围时,便会出现二次释放。
-
C++ 还包含“std::move”,它用于指示何时可以移动某个值。如果示例为“s2 = std::move(s1)”,则不会发生堆分配。移动后,“s1”将处于有效但未指定的状态。与 Rust 不同,程序员可以继续使用“s1”。
-
与 Rust 不同,使用 C++ 时,“=”可以运行任意代码,具体取决于要复制或移动的类型。