3.2 移动语义与复制语义
听
在 Rust 中,当一个值被赋给另一个变量或作为参数传递给函数时,其所有权可能被移动(move)或复制(copy)。这一行为取决于该值的类型是否实现了 Copy trait。
移动语义(Move)
对于存储在堆上的复杂类型(如 String、Vec<T> 等),Rust 默认采用移动语义。这意味着当变量被赋值给另一个变量时,所有权会从原变量转移到新变量,原变量将不再有效。
例如:
let s1 = String::from("hello");
let s2 = s1; // 所有权从 s1 移动到 s2
// println!("{}", s1); // 编译错误:s1 已失效
println!("{}", s2); // 正常工作
这种设计防止了多个所有者同时持有同一块堆内存,从而避免了双重释放(double free)等内存安全问题。移动操作本身不涉及堆数据的复制,仅转移栈上的指针、长度和容量信息,因此开销极小。
复制语义(Copy)
对于简单的、存储在栈上的类型(如整数、浮点数、布尔值、字符等),Rust 允许它们在赋值时被复制而非移动。这些类型都实现了 Copy trait。
let x = 5;
let y = x; // x 被复制,x 和 y 都有效
println!("x = {}, y = {}", x, y); // 正常输出
由于栈上数据的复制成本很低,且不会引发内存安全问题,Rust 默认对这类类型启用复制语义。
哪些类型实现了 Copy?
一般来说,以下类型是 Copy 的:
- 所有整数类型(
i32、u8等) - 布尔类型(
bool) - 浮点类型(
f32、f64) - 字符类型(
char) - 元组,但仅当其所有元素都是
Copy类型时(如(i32, bool)是Copy,而(i32, String)不是)
你不能为包含堆分配数据(如 String 或 Vec<T>)的类型实现 Copy,因为这可能导致浅拷贝带来的悬垂指针或双重释放问题。
函数调用中的移动与复制
当将变量传入函数时,同样遵循移动或复制规则:
fn takes_ownership(s: String) {
println!("{}", s);
} // s 离开作用域并被 drop
fn makes_copy(x: i32) {
println!("{}", x);
} // x 是 Copy 类型,离开作用域无影响
let s = String::from("hello");
takes_ownership(s); // s 被移动,之后不可用
let x = 5;
makes_copy(x); // x 被复制,之后仍可用
println!("{}", x);
小结
Rust 通过区分移动和复制语义,在保证内存安全的同时兼顾性能。移动适用于堆分配的复杂类型,防止资源竞争;复制适用于轻量级栈类型,提供便利性。理解何时发生移动、何时发生复制,有助于编写符合所有权规则的正确代码,并减少不必要的克隆操作。
#Rust 入门教程
分享于 1 周前
上一篇:3.1 所有权规则与栈/堆内存模型
下一篇:3.3 引用与借用