所有权、移动、复制与析构
本章目标
这一章进入 Rust 最核心的规则:每个值都有所有者,所有者离开作用域时值被释放,赋值和传参可能发生移动。你会理解 Copy 为什么特殊,以及为什么这些规则能替代垃圾回收。
它是什么
所有权是 Rust 管理内存的制度。每个值在同一时刻只有一个所有者。所有者离开作用域,值就会被清理。
{
let text = String::from("hello");
}
当程序离开花括号,text 被释放,它持有的堆内存也被释放。
为什么需要
没有所有权,程序需要用垃圾回收或手动释放管理内存。垃圾回收简单但有运行时成本;手动释放灵活但容易出现悬垂指针、重复释放、泄漏。
Rust 的选择是:编译期追踪谁拥有资源,谁负责释放。这样大部分错误在编译时被挡住。
怎么使用
移动示例:
let a = String::from("hello");
let b = a;
println!("{b}");
a 的所有权移动给 b,之后不能再使用 a。如果你想保留两份独立字符串,需要显式克隆:
let a = String::from("hello");
let b = a.clone();
println!("{a} {b}");
逐行解释
fn take(text: String) {
println!("{text}");
}
let name = String::from("RinBlog");
take(name);
fn take(text: String)表示函数接收一个拥有所有权的String。- 调用
take(name)时,name的所有权移动进函数参数text。 - 函数体中可以使用
text。 - 函数结束时,
text离开作用域,字符串被释放。 - 调用后不能再使用
name,因为它已经不再拥有值。
如果函数只是读取字符串,应改成接收 &str 或 &String,也就是借用。
常见坑
String、Vec<T>、HashMap<K, V>默认移动,不自动复制。- 整数、布尔值、字符等简单类型通常实现
Copy,赋值后还能继续用原变量。 clone()是显式复制,能解决所有权问题,但可能有性能成本。- 移动不是运行时搬运大块内存,很多时候只是把栈上的指针、长度、容量转移给新变量。
练习
- 写一个函数接收
String,调用后尝试继续使用原变量,观察报错。 - 把函数参数改成
&str,再观察是否能继续使用原变量。 - 对
String使用clone(),比较行为。 - 对
i32做同样实验,理解Copy。
造轮子任务
写一个资源盒子 struct Resource { name: String },实现一个函数 consume(resource: Resource)。打印构造、移动、消费的过程。目标是用日志观察所有权从一个名字转到另一个名字。
小结
所有权回答“谁负责释放”。移动让责任转移,Copy 让简单值复制,clone 让复杂值显式复制。理解这一章后,后面的借用规则才有意义。