Rust 所有权 + 借用 + 引用
深入理解所有权机制、Move 语义、借用规则、可变引用与不可变引用
日期: 2026-04-13 方向: Rust 阶段: 第一阶段:基础构建 标签: #rust #ownership #borrowing #references #memory-safety
今日目标
| 类型 | 内容 |
|---|---|
| 学习 | 深入理解所有权机制、Move 语义、借用规则、可变引用与不可变引用 |
| 实操 | 编写多个示例展示所有权转移和借用,修复常见编译器错误 |
| 产出 | 所有权模型心智模型 + 常见模式代码集 |
一、为什么所有权如此重要
所有权(Ownership)是 Rust 最独特也是最核心的概念。它让 Rust 在编译时就能保证内存安全,无需垃圾回收器(GC),也无需手动管理内存。
1.1 其他语言的内存管理方式
| 方式 | 语言 | 优点 | 缺点 |
|---|---|---|---|
| 手动管理 | C/C++ | 精确控制,高性能 | 内存泄漏、悬空指针、双重释放 |
| 垃圾回收 | Java/Go/JS | 开发者无需关心 | GC 暂停、内存占用大、不可预测延迟 |
| 引用计数 | Python/Swift | 相对简单 | 循环引用问题、引用计数开销 |
| 所有权 | Rust | 编译时保证安全,零运行时开销 | 学习曲线陡峭 |
1.2 对区块链的意义
在区块链开发中,内存安全至关重要:
- Solana 程序中的数据必须精确管理,不能有内存泄漏
- 智能合约 bug 可能导致数百万美元损失
- 零成本抽象让 Rust 程序既安全又高效,适合高吞吐量区块链
二、所有权三大规则
规则 1:Rust 中每个值都有一个"所有者"(owner)
规则 2:任何时刻,值只能有一个所有者
规则 3:当所有者离开作用域时,值被自动释放(drop)
2.1 作用域与 Drop
fn main() {
{
let s = String::from("hello"); // s 从此处开始有效
println!("{}", s); // s 在这里可用
} // s 离开作用域,String 的内存被自动释放(调用 drop)
// println!("{}", s); // ❌ 编译错误:s 不再存在
}
2.2 栈数据 vs 堆数据
fn main() {
// 栈数据:固定大小类型,直接复制
let x = 5; // i32,存储在栈上
let y = x; // 复制值,x 和 y 都等于 5
println!("x = {}, y = {}", x, y); // ✅ 都能用
// 堆数据:String 存储在堆上
let s1 = String::from("hello");
let s2 = s1; // 所有权转移(Move)!s1 不再有效
// println!("{}", s1); // ❌ error: borrow of moved value: `s1`
println!("{}", s2); // ✅ s2 是新的所有者
}
内存图解:
栈复制(Copy):
x = 5 → x = 5 (仍然有效)
y = 5 (独立副本)
所有权转移(Move):
s1 = String{ptr, len, cap} → s1 = [无效]
s2 = String{ptr, len, cap}
堆: "hello" 堆: "hello" (同一块内存)
2.3 Move 语义详解
fn main() {
let s = String::from("ownership demo");
// 赋值触发 Move
let s2 = s;
// s 已经无效
// 传参也触发 Move
takes_ownership(s2);
// s2 已经无效
// println!("{}", s2); // ❌ error: borrow of moved value
// 返回值也转移所有权
let s3 = gives_ownership();
println!("{}", s3); // ✅ s3 拥有返回的 String
// 接收所有权再归还
let s4 = String::from("take and give back");
let s5 = takes_and_gives_back(s4);
// s4 无效,s5 有效
println!("{}", s5);
}
fn takes_ownership(s: String) {
println!("Got: {}", s);
} // s 在这里被 drop
fn gives_ownership() -> String {
let s = String::from("yours");
s // 返回 s,所有权转移给调用者
}
fn takes_and_gives_back(s: String) -> String {
println!("Processing: {}", s);
s // 归还所有权
}
2.4 Copy trait
实现了 Copy trait 的类型在赋值时会复制而非移动:
fn main() {
// 这些类型实现了 Copy,赋值时复制:
let a: i32 = 42;
let b = a; // 复制,a 仍然有效
println!("{} {}", a, b); // ✅
let c: f64 = 3.14;
let d = c; // 复制
println!("{} {}", c, d); // ✅
let e: bool = true;
let f = e; // 复制
println!("{} {}", e, f); // ✅
let g: char = 'A';
let h = g; // 复制
println!("{} {}", g, h); // ✅
// 元组中全是 Copy 类型也是 Copy
let tup = (1, 2.0, true);
let tup2 = tup; // 复制
println!("{:?} {:?}", tup, tup2); // ✅
// 这些类型不是 Copy,赋值时 Move:
// String, Vec<T>, HashMap<K,V>, Box<T>, 等堆分配类型
}
2.5 Clone:显式深拷贝
fn main() {
let s1 = String::from("deep copy");
let s2 = s1.clone(); // 显式深拷贝,堆上数据也被复制
println!("s1 = {}, s2 = {}", s1, s2); // ✅ 两个都有效
// clone 的成本
let big_vec = vec![0u8; 1_000_000]; // 1MB 数据
let big_vec_copy = big_vec.clone(); // 复制 1MB!性能开销大
// 所以 Rust 不会默认 clone,需要开发者显式选择
}
三、借用(Borrowing)与引用(References)
每次要用某个值就 Move 所有权太不方便了。借用允许你使用值而不获取所有权。
3.1 不可变引用 (&T)
fn main() {
let s = String::from("hello");
// 传递引用而非所有权
let len = calculate_length(&s); // & 创建一个引用
println!("Length of '{}' is {}", s, len); // ✅ s 仍然有效!
// 可以同时有多个不可变引用
let r1 = &s;
let r2 = &s;
let r3 = &s;
println!("{} {} {}", r1, r2, r3); // ✅ 完全没问题
}
fn calculate_length(s: &String) -> usize { // s 是 String 的引用
s.len()
// 函数结束时,s(引用)被销毁,但原始数据不受影响
// 因为函数不拥有这个值
}
3.2 可变引用 (&mut T)
fn main() {
let mut s = String::from("hello");
change(&mut s); // 传递可变引用
println!("{}", s); // "hello, world"
}
fn change(s: &mut String) {
s.push_str(", world"); // 可以修改借用的值
}
3.3 借用规则 — 核心!
规则 A:在同一时间,你可以拥有:
- 任意数量的不可变引用(&T) ← 多个读者
OR
- 恰好一个可变引用(&mut T) ← 一个写者
规则 B:引用必须始终有效(不能有悬空引用)
这两条规则在编译时强制执行,保证了:
- 不会出现数据竞争(data race)
- 不会出现悬空引用(dangling reference)
3.4 规则 A 的实际例子
fn main() {
let mut s = String::from("hello");
// ✅ 多个不可变引用 OK
let r1 = &s;
let r2 = &s;
println!("{} {}", r1, r2);
// ✅ 在不可变引用不再使用后,可以创建可变引用
// (Rust 的"非词法生命周期" NLL 特性)
let r3 = &mut s;
r3.push_str(" world");
println!("{}", r3);
// ❌ 不能同时有可变和不可变引用
let mut data = String::from("test");
let ref1 = &data;
// let ref2 = &mut data; // ❌ cannot borrow as mutable, already borrowed as immutable
println!("{}", ref1);
// 但 ref1 在这之后不再使用,所以之后可以:
let ref2 = &mut data; // ✅ 因为 ref1 的生命周期已经结束
ref2.push_str("!");
println!("{}", ref2);
}
3.5 规则 B:悬空引用保护
// ❌ Rust 不允许返回悬空引用
// fn dangle() -> &String {
// let s = String::from("hello");
// &s // ❌ 返回了局部变量的引用
// // s 在函数结束时被释放,引用指向无效内存
// }
// ✅ 正确做法:返回所有权
fn no_dangle() -> String {
let s = String::from("hello");
s // 转移所有权,不是返回引用
}
四、常见编译器错误及修复
4.1 Use after move
// ❌ 错误代码
fn problem_1() {
let name = String::from("Alice");
let greeting = format!("Hello, {}", name); // format! 不会 move
let name2 = name; // Move!
// println!("{}", name); // ❌ value used after move
println!("{}", name2);
println!("{}", greeting);
}
// ✅ 修复方法 1:使用引用
fn fix_1a() {
let name = String::from("Alice");
let greeting = format!("Hello, {}", name);
print_name(&name); // 借用,不转移所有权
println!("{}", name); // ✅ name 仍然有效
println!("{}", greeting);
}
fn print_name(name: &String) {
println!("Name: {}", name);
}
// ✅ 修复方法 2:克隆
fn fix_1b() {
let name = String::from("Alice");
let name2 = name.clone();
println!("{} {}", name, name2); // ✅ 两个都有效
}
4.2 Mutable borrow while immutable borrow exists
// ❌ 错误代码
fn problem_2() {
let mut v = vec![1, 2, 3];
let first = &v[0]; // 不可变借用
v.push(4); // ❌ 需要可变借用,但 first 还在用
// println!("{}", first); // first 的生命周期延伸到这里
}
// ✅ 修复方法:在可变操作前完成不可变使用
fn fix_2() {
let mut v = vec![1, 2, 3];
let first = v[0]; // 复制值(i32 实现了 Copy)
v.push(4); // ✅ 没有活跃的借用
println!("first = {}, vec = {:?}", first, v);
}
// 或者调整顺序
fn fix_2b() {
let mut v = vec![1, 2, 3];
let first = &v[0];
println!("first = {}", first); // 在这里使用完 first
// first 的生命周期到此结束(NLL)
v.push(4); // ✅ 现在可以可变借用
println!("vec = {:?}", v);
}
4.3 Cannot move out of borrowed content
// ❌ 错误代码
fn problem_3(v: &Vec<String>) -> String {
// v[0] // ❌ cannot move out of `v[0]` which is behind a shared reference
// 因为 v 只是借用,不能从中取出(Move)元素
// ✅ 修复方法 1:克隆
v[0].clone()
// ✅ 修复方法 2:返回引用
// &v[0] // 返回类型改为 &String
}
4.4 Multiple mutable borrows
// ❌ 错误代码
fn problem_4() {
let mut s = String::from("hello");
let r1 = &mut s;
// let r2 = &mut s; // ❌ cannot borrow `s` as mutable more than once
r1.push_str(" world");
}
// ✅ 修复:使用作用域分隔
fn fix_4() {
let mut s = String::from("hello");
{
let r1 = &mut s;
r1.push_str(" world");
} // r1 在这里结束
{
let r2 = &mut s;
r2.push_str("!");
} // r2 在这里结束
println!("{}", s); // "hello world!"
}
五、代码实战:模拟代币转账系统
use std::collections::HashMap;
/// 代币账本 - 演示所有权和借用在实际场景中的应用
struct TokenLedger {
name: String,
symbol: String,
balances: HashMap<String, u64>,
total_supply: u64,
}
impl TokenLedger {
/// 创建新的代币账本(所有权转移:name 和 symbol Move 到 struct 中)
fn new(name: String, symbol: String) -> Self {
TokenLedger {
name,
symbol,
balances: HashMap::new(),
total_supply: 0,
}
}
/// 铸造代币(&mut self:需要可变借用来修改状态)
fn mint(&mut self, to: &str, amount: u64) {
let balance = self.balances.entry(to.to_string()).or_insert(0);
*balance += amount; // 解引用后修改
self.total_supply += amount;
println!("[MINT] {} {} → {}", amount, self.symbol, to);
}
/// 转账(&mut self + 多个 &str 借用)
fn transfer(&mut self, from: &str, to: &str, amount: u64) -> Result<(), String> {
// 先检查余额(不可变读取)
let from_balance = self.balance_of(from);
if from_balance < amount {
return Err(format!(
"Insufficient balance: {} has {}, needs {}",
from, from_balance, amount
));
}
// 修改余额(可变操作)
// 注意:不能同时持有两个可变引用到 HashMap 的不同 entry
// 所以我们先计算新值,再写入
let new_from = from_balance - amount;
let to_balance = self.balance_of(to);
let new_to = to_balance + amount;
self.balances.insert(from.to_string(), new_from);
self.balances.insert(to.to_string(), new_to);
println!(
"[TRANSFER] {} {} : {} → {}",
amount, self.symbol, from, to
);
Ok(())
}
/// 查询余额(&self:只需要不可变借用)
fn balance_of(&self, account: &str) -> u64 {
// HashMap::get 返回 Option<&V>
// copied() 将 Option<&u64> 转为 Option<u64>
// unwrap_or(0) 如果 None 返回 0
self.balances.get(account).copied().unwrap_or(0)
}
/// 获取代币信息(&self:不可变借用)
fn info(&self) -> String {
format!(
"{} ({}) - Total Supply: {}",
self.name, self.symbol, self.total_supply
)
}
/// 获取所有持有者(返回引用的迭代器)
fn holders(&self) -> Vec<(&String, &u64)> {
self.balances.iter().collect()
}
}
fn main() {
// 创建代币(String 的所有权 Move 到 TokenLedger)
let name = String::from("MomoToken");
let symbol = String::from("MOMO");
let mut ledger = TokenLedger::new(name, symbol);
// 此时 name 和 symbol 已经无效(被 Move 了)
// println!("{}", name); // ❌ error: borrow of moved value
// 铸造
ledger.mint("Alice", 1_000_000);
ledger.mint("Bob", 500_000);
ledger.mint("Charlie", 250_000);
// 打印信息(&self 借用)
println!("\n{}", ledger.info());
// 查询余额(&self 借用 + &str 借用)
println!("\nBalances:");
println!(" Alice: {}", ledger.balance_of("Alice"));
println!(" Bob: {}", ledger.balance_of("Bob"));
println!(" Charlie: {}", ledger.balance_of("Charlie"));
// 转账(&mut self 借用)
println!();
match ledger.transfer("Alice", "Bob", 200_000) {
Ok(()) => println!("Transfer successful!"),
Err(e) => println!("Transfer failed: {}", e),
}
// 失败的转账
match ledger.transfer("Charlie", "Alice", 999_999) {
Ok(()) => println!("Transfer successful!"),
Err(e) => println!("Transfer failed: {}", e),
}
// 打印所有持有者
println!("\nAll holders:");
for (addr, balance) in ledger.holders() {
println!(" {}: {}", addr, balance);
}
// 演示所有权转移
let ledger2 = ledger; // Move! ledger 不再有效
// println!("{}", ledger.info()); // ❌ error: borrow of moved value
println!("\nFinal: {}", ledger2.info());
}
六、所有权模式速查表
┌─────────────────────────────────────────────────────────┐
│ Rust 所有权决策树 │
├─────────────────────────────────────────────────────────┤
│ │
│ 我需要使用一个值 ─┬─ 只需要读取? │
│ │ ├─ Yes → 传递 &T (不可变借用) │
│ │ └─ No, 需要修改? │
│ │ ├─ Yes → 传递 &mut T (可变借用)│
│ │ └─ No, 需要拥有它? │
│ │ ├─ Yes → 传递 T (Move) │
│ │ └─ 需要副本? │
│ │ └─ Yes → .clone() │
│ │ │
│ 函数返回值 ───────┬─ 返回堆数据?→ 返回 T (转移所有权) │
│ ├─ 返回引用?→ 需要生命周期标注 │
│ └─ 返回 Copy 类型?→ 直接返回 │
└─────────────────────────────────────────────────────────┘
七、关键要点总结
| 要点 | 说明 |
|---|---|
| 每个值只有一个所有者 | 赋值和传参会 Move 所有权 |
| & 是不可变借用 | 可以有多个同时存在 |
| &mut 是可变借用 | 同一时间只能有一个 |
| 不能混用 & 和 &mut | 编译器保证无数据竞争 |
| Copy trait 类型复制 | i32/f64/bool/char 等基础类型 |
| String 不是 Copy | 赋值 Move,需要 .clone() 深拷贝 |
| NLL (Non-Lexical Lifetimes) | 引用的生命周期到最后一次使用为止 |
八、常见误区
误区 1:以为所有赋值都是 Move
let x: i32 = 5;
let y = x; // 这是 Copy,不是 Move!
println!("{}", x); // ✅ 完全 OK
// 只有堆分配类型(String, Vec, HashMap等)才 Move
误区 2:以为引用就是指针
// Rust 的引用是"有保证的指针":
// - 不可能是 null
// - 不可能是悬空的
// - 编译器保证引用在使用期间指向的数据有效
// C/C++ 的指针则没有这些保证
误区 3:过度使用 clone
// 新手常见:到处 .clone() 让编译器不报错
let s = String::from("hello");
do_something(s.clone()); // 不好!如果函数不需要所有权,用引用
do_something_better(&s); // 好!零成本借用
// clone 在堆上复制数据,有性能开销
误区 4:混淆 &String 和 &str
// &String: 引用一个 String
// &str: 字符串切片(更通用)
fn print(s: &str) { // 推荐用 &str,因为 &String 可以自动转为 &str
println!("{}", s);
}
let owned = String::from("hello");
print(&owned); // ✅ &String 自动解引用为 &str
print("hello"); // ✅ &str 字面量直接传入
九、与 Solidity 的对比
| 概念 | Solidity | Rust |
|---|---|---|
| 内存管理 | EVM 自动管理 + gas 费 | 所有权系统,编译时管理 |
| 数据位置 | storage/memory/calldata | 栈/堆/引用 |
| 可变性 | 默认可变 | 默认不可变 |
| 引用语义 | storage 引用 vs memory 复制 | & 借用 vs Move |
| 深拷贝 | memory 类型自动复制 | 需要显式 .clone() |
| 安全保证 | 运行时检查(gas 消耗) | 编译时检查(零开销) |
Solidity 的 storage 引用类似 Rust 的 &mut:
// Solidity
function updateUser(uint id) internal {
User storage user = users[id]; // storage 引用,修改会反映到存储
user.balance += 100;
}
// Rust 类比
fn update_user(users: &mut HashMap<u32, User>, id: u32) {
if let Some(user) = users.get_mut(&id) { // &mut 引用
user.balance += 100;
}
}
十、面试关联
Q: 解释 Rust 的所有权系统。为什么它对区块链开发重要?
A: Rust 的所有权系统通过三条规则(唯一所有者、值离开作用域自动释放、借用规则)在编译时保证内存安全。对区块链的意义:(1) 零运行时开销——没有 GC 暂停,Solana 等高吞吐量链需要可预测的执行时间。(2) 编译时 bug 检测——数据竞争、悬空指针等 bug 在编译阶段就被捕获,减少智能合约安全漏洞。(3) 精确的资源控制——与区块链上精确的状态管理理念一致。
Q: Move 语义和 Copy 语义有什么区别?
A: Move 语义转移所有权(原变量失效),Copy 语义创建独立副本(两个变量都有效)。实现了 Copy trait 的类型(如整数、布尔、浮点)赋值时自动复制;堆分配类型(如 String、Vec)赋值时 Move。需要深拷贝堆类型时使用 .clone()。这个设计让开发者明确知道每次赋值的成本。
Q: 为什么不能同时有可变引用和不可变引用?
A: 这防止了数据竞争(data race)——一种并发 bug。数据竞争需要三个条件同时满足:两个或多个指针访问同一数据、至少一个在写、没有同步机制。Rust 的借用规则在编译时禁止了前两个条件同时出现,从根本上消除了数据竞争。这在多线程区块链节点中尤其重要。
十一、参考资源
| 资源 | 链接 | 说明 |
|---|---|---|
| The Rust Book Ch.4 | https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html | 所有权章节,必读 |
| Rust by Example: Ownership | https://doc.rust-lang.org/rust-by-example/scope/move.html | 代码示例 |
| Visualizing Ownership | https://rufflewind.com/2017-02-15/rust-move-copy-borrow | 可视化讲解 |
| Rustlings move_semantics | rustlings 的 move_semantics 系列练习 | 动手练习 |
| Too Many Linked Lists | https://rust-unofficial.github.io/too-many-lists/ | 通过链表深入理解所有权 |