讲解
所有权是 Rust 最独特、也最需要花时间消化的机制。规则只有三条:一、每个值都有一个所有者(owner);二、同一时刻只能有一个所有者;三、所有者离开作用域,值被自动释放(drop)。没有垃圾回收器,也没有手动 free——内存释放在编译期就被精确安排好了。
关键在「赋值即移动」(move)。对 String 这种持有堆内存的类型,let s2 = s1 不是复制内容,而是把堆数据的所有权从 s1 转移给 s2,s1 立刻失效——之后再用 s1 就是编译错误。这么做的理由:如果两个变量都认为自己是所有者,离开作用域时同一块内存会被释放两次(double free),Rust 用「移动后旧变量失效」在编译期根除了这个问题。想要真正的深拷贝,显式调用 .clone()——它有堆分配开销,所以必须显式写。
反过来,i32、bool、char、f64 这类完全存在栈上的固定大小类型实现了 Copy 特征,赋值时是复制而不是移动,旧变量照常可用。这就是为什么 let y = x 对数字不报错、对 String 报错。函数传参和返回值遵循同样的规则:把 String 传给函数就是把所有权交出去(函数返回可以再拿回来);传 i32 则是复制。
示例
移动、失效与显式 clone:
fn main() {
let s1 = String::from("所有权");
let s2 = s1; // 所有权从 s1 移动到 s2,s1 从此失效
println!("s2 = {s2}");
let a = String::from("原数据");
let b = a.clone(); // 深拷贝:a 和 b 各自拥有一份
println!("a = {a}, b = {b}");
assert_eq!(s2, "所有权");
assert_eq!(a, b);
}
如果在移动之后继续使用旧变量,编译器会拒绝(构建脚本会验证这段确实编译失败):
// compile-fail: 移动后使用旧变量
fn main() {
let s1 = String::from("hello");
let s2 = s1;
println!("{s1}");
println!("{s2}");
}
报错大意(E0382,借用了一个已移动的值):
error[E0382]: borrow of moved value: `s1`
--> main.rs:4:20
|
2 | let s1 = String::from("hello");
| -- move occurs because `s1` has type `String`,
| which does not implement the `Copy` trait
3 | let s2 = s1;
| -- value moved here
4 | println!("{s1}");
| ^^^^ value borrowed here after move
Copy 类型的赋值是复制;函数传参会移动所有权:
fn takes_ownership(s: String) -> usize {
s.len()
} // s 在这里离开作用域并被释放
fn makes_copy(n: i32) -> i32 {
n * 2
}
fn main() {
let x = 5;
let doubled = makes_copy(x); // i32 是 Copy,x 仍然有效
assert_eq!(x, 5);
assert_eq!(doubled, 10);
let text = String::from("交出所有权");
let len = takes_ownership(text);
// text 在这里已经失效,不能再使用
println!("长度是 {len}");
assert_eq!(len, 15); // 5 个汉字 × 3 字节
}
常见坑
- 以为 = 总是复制:对 String、Vec 这类堆类型,= 是移动;对数字、bool、char 是复制。判断标准:类型是否实现了 Copy。
- 到处 clone 求太平:clone 是真实的堆分配开销,大量滥用会拖慢程序。多数「不得不 clone」的场景其实该用引用(下一章)。
- 把 &str 字面量当 String:"hello" 是 &str(指向程序二进制的字符串切片),String 是拥有堆内存的类型;两者方法集不同,转换用 String::from 或 .to_string()。
- 传参后还想用原值:函数拿走所有权后就别再用了——要么让函数返回回来,要么(更好的方案)传引用。
小结
三规则:一个值一个所有者、所有者离开即释放、赋值即移动(Copy 类型除外)。移动语义杜绝了二次释放,也让所有权流向清晰可读。但每次都交出所有权太笨拙——下一章的引用与借用才是日常写法。