返回 SC 笔记
SC Day 4

Rust 所有权 + 借用 + 引用

深入理解所有权机制、Move 语义、借用规则、可变引用与不可变引用

2026-04-13
第一阶段:基础构建
rustownershipborrowingreferencesmemory-safety

日期: 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 的对比

概念SolidityRust
内存管理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 的类型(如整数、布尔、浮点)赋值时自动复制;堆分配类型(如 StringVec)赋值时 Move。需要深拷贝堆类型时使用 .clone()。这个设计让开发者明确知道每次赋值的成本。

Q: 为什么不能同时有可变引用和不可变引用?

A: 这防止了数据竞争(data race)——一种并发 bug。数据竞争需要三个条件同时满足:两个或多个指针访问同一数据、至少一个在写、没有同步机制。Rust 的借用规则在编译时禁止了前两个条件同时出现,从根本上消除了数据竞争。这在多线程区块链节点中尤其重要。


十一、参考资源

资源链接说明
The Rust Book Ch.4https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html所有权章节,必读
Rust by Example: Ownershiphttps://doc.rust-lang.org/rust-by-example/scope/move.html代码示例
Visualizing Ownershiphttps://rufflewind.com/2017-02-15/rust-move-copy-borrow可视化讲解
Rustlings move_semanticsrustlings 的 move_semantics 系列练习动手练习
Too Many Linked Listshttps://rust-unofficial.github.io/too-many-lists/通过链表深入理解所有权