第 2 章 所有权系统

Chapter-02 所有权系统(Ownership)

2.1 所有权概述

欢迎来到 Rust 最独特、最核心的概念——所有权系统(Ownership System)!

如果说 Rust 是一座大厦,那所有权就是这座大厦的地基。没有它,Rust 就不可能实现"内存安全 + 无 GC + 零成本抽象"的三位一体神话。

想象一下,如果有一个神奇的管家,能在你写代码的时候帮你记住:

  • 这块内存是谁的?
  • 什么时候该释放这块内存?
  • 谁能访问这块内存?

这个管家就是 Rust 的所有权系统!它存在于编译时,不需要运行时开销,却能杜绝大多数内存安全问题!

2.1.1 什么是所有权

2.1.1.1 所有权三条规则

Rust 的所有权系统有三条铁律,必须牢记:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
fn main() {
    // 规则1:每个值都有一个所有者(owner)
    let s = String::from("hello"); // s 是这个 String 的所有者
    
    // 规则2:每个值同时只能有一个所有者
    let s2 = s; // 所有权从 s 转移(move)到 s2
    // println!("{}", s); // 编译错误!s 已经无效了!
    println!("{}", s2); // 正确!s2 才是合法所有者
    
    // 规则3:当所有者离开作用域时,值会被丢弃(drop)
    {
        let temp = String::from("临时数据");
        println!("{}", temp);
    } // temp 在这里被自动释放
    // println!("{}", temp); // 编译错误!temp 已经不存在了
}

简单来说:谁创建,谁拥有,谁负责释放。—— 多么简洁公平的所有制!不像你室友,借了你的薯片,吃完了还假装不知道是谁吃的。

2.1.1.2 所有权与内存管理的关系

传统的内存管理方式有三种:

方式代表语言原理优点缺点
手动管理C/C++程序员手动 malloc/free性能极致容易出错、内存泄漏
GCJava/Go/Python运行时自动追踪并回收安全有性能开销、程序停顿
所有权Rust编译时分析,编译器插入释放代码安全 + 高效学习曲线陡峭
1
2
3
4
5
// Rust 的内存管理:编译器帮你生成释放代码
fn main() {
    let s = String::from("hello");
    // 编译器自动插入:s.drop() 或 free(s)
} // 作用域结束,s 被自动清理
2.1.1.3 堆上数据 vs 栈上数据的区别

在 Rust 里,栈上数据(如整数、布尔等固定大小的类型)存储在栈内存,堆上数据(如 String、Vec 等动态大小的类型)存储在堆内存。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
fn main() {
    // 栈上数据:存储在栈内存,复制成本低
    let x = 5; // i32 是 Copy 类型,值直接复制
    let y = x; // 完整复制了一份
    println!("x = {}, y = {}", x, y); // x = 5, y = 5
    
    // 堆上数据:存储在堆内存,复制成本高
    let s1 = String::from("hello"); // String 存的是指针,在栈上;数据在堆上
    let s2 = s1; // 只是复制了指针(所有权转移),不是整个字符串!
    // 移动后 s1 失效了,编译器保证它不会被用来访问数据,自然不会双重释放
    // println!("{}", s1); // 编译错误!s1 的所有权已转移
    println!("{}", s2); // 正确!
}
graph TB
    subgraph 栈内存
        A1[变量 x: i32 = 5]
        A2[变量 y: i32 = 5]
    end
    
    subgraph 堆内存
        B1["字符串数据<br/> hello"]
    end
    
    subgraph 栈内存
        S1[指针 s1<br/>指向堆数据]
        S2[指针 s2<br/>指向堆数据]
    end
    
    S1 -.-> B1
    S2 -.-> B1
    
    style B1 fill:#f9f

2.1.2 内存管理方式对比

2.1.2.1 垃圾回收(GC)
1
2
3
4
5
6
7
8
9
// Java/Python:GC 自动回收
public class MemoryExample {
    public static void main(String[] args) {
        String s = new String("hello");
        s = null; // 没人引用了,GC 会回收这块内存
        // 但 GC 什么时候运行是不确定的
        // 而且 GC 会带来"Stop The World"停顿
    }
}

GC 的问题:

  • 不确定的回收时机:你不知道内存什么时候被释放
  • 运行时开销:GC 线程持续消耗 CPU
  • 内存占用高:GC 需要堆空间来追踪对象
  • 程序停顿:GC 回收时程序会暂停
2.1.2.2 手动管理
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// C:手动管理内存
char* strdup(const char* s) {
    char* p = malloc(strlen(s) + 1); // 分配内存
    if (p) {
        strcpy(p, s); // 复制数据
    }
    return p; // 返回指针
} // 调用者必须记得 free(p),否则内存泄漏!

void leak_example() {
    char* s = strdup("hello");
    // 忘记 free(s) 就退出了?内存泄漏!
    // 或者 free(s) 之后又访问?悬垂指针!
}

手动管理的问题:

  • 内存泄漏:忘记释放
  • 悬垂指针:释放后继续访问
  • 双重释放:同一块内存释放两次
  • 数据竞争:多线程同时访问
2.1.2.3 所有权系统
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// Rust:编译器保证内存安全
fn main() {
    let s = String::from("hello");
    // ... 使用 s ...
} // 作用域结束,s 自动释放
// 没有 GC,没有手动 free,但绝对不会泄漏!

fn dangle() -> &String {
    let s = String::from("hello");
    &s // 编译错误!不能返回局部变量的引用!
} // s 在这里被释放,返回的引用变成悬垂引用

Rust 的保证:

  • 无内存泄漏:编译器保证每个值都被正确释放
  • 无悬垂指针:引用必须始终有效
  • 无数据竞争:借用规则保证线程安全
  • 零运行时开销:所有检查都在编译时完成
2.1.2.4 引用计数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// Swift:自动引用计数(ARC)
class Person {
    let name: String
    init(name: String) {
        self.name = name
    }
    deinit {
        print("\(name) 被释放")
    }
}

var person1: Person? = Person(name: "Alice")
var person2 = person1 // 引用计数 +1
var person3 = person1 // 引用计数 +1

person1 = nil // 引用计数 -1,但还有 person2、person3
person2 = nil // 引用计数 -1,但还有 person3
person3 = nil // 引用计数 -1,变为 0,对象被释放

// ARC 比 GC 轻量,但仍然有运行时开销
// 而且循环引用会导致内存泄漏(需要 weak 打破)

引用计数的问题:

  • 循环引用会导致内存泄漏
  • 维护引用计数有运行时开销
  • 不确定何时释放(最后一个引用消失时)

2.1.3 为什么 Rust 选择所有权模型

2.1.3.1 零成本抽象的原则

Rust 的口号是"零成本抽象"——如果你不使用某个特性,就不会有任何开销。

1
2
3
4
5
6
7
8
9
// 抽象示例
fn sum(numbers: &[i32]) -> i32 {
    numbers.iter().sum()
}

fn main() {
    let arr = [1, 2, 3, 4, 5];
    println!("{}", sum(&arr)); // 15
}

这段代码看起来很抽象(使用了迭代器、闭包等),但编译后的性能和手写循环几乎一样!为什么?因为这些抽象都是"零成本"的——编译器会内联并优化掉所有开销。

所有权系统也一样:

  • 如果你只使用栈上的 Copy 类型,编译器和 C 代码一样高效
  • 如果你使用堆上的 Move 类型,编译器确保安全释放,没有 GC 开销
2.1.3.2 编译期内存安全检查
1
2
3
4
5
fn main() {
    let s = String::from("hello");
    let s2 = s;
    // println!("{}", s); // 编译错误!所有权已转移
}

这个错误是在编译时发现的,而不是运行时!这意味着:

  • 不会产生运行时开销
  • 不会产生安全漏洞
  • 不会产生内存泄漏
2.1.3.3 无运行时开销
1
2
3
4
5
6
7
8
fn main() {
    let s = String::from("hello");
    // 编译后相当于 C 代码:
    // char* s = malloc(6);
    // strcpy(s, "hello");
    // ...
    // free(s);
} // 作用域结束,自动释放,没有 GC 暂停!

Rust 的目标:既有 C/C++ 的性能,又有 Python/Java 的安全

mermaid

flowchart TD
    A[Rust 内存管理] --> B[编译时分析]
    A --> C[无 GC]
    A --> D[无手动管理]
    
    B --> E[所有权检查]
    B --> F[借用检查]
    B --> G[生命周期检查]
    
    E --> H[每个值有唯一所有者]
    F --> I[引用有效性保证]
    G --> J[无悬垂指针]
    
    C --> K[无 Stop The World]
    C --> L[确定性的释放时机]
    
    D --> M[无 use-after-free]
    D --> N[无 double-free]
    
    style A fill:#ff6b6b
    style K fill:#51cf66
    style L fill:#51cf66
    style M fill:#51cf66
    style N fill:#51cf66

2.2 移动语义(Move Semantics)

移动语义是 Rust 所有权系统的核心概念之一。当我们把一个值赋给另一个变量、传给函数、或者从函数返回时,所有权可能会"移动"。理解移动语义,是掌握 Rust 的关键!

2.2.1 移动的定义

2.2.1.1 移动语义的概念

移动(Move)意味着所有权的转移。当一个值被"移动"后,原来的变量就不再有效了。

1
2
3
4
5
6
7
8
9
fn main() {
    let s1 = String::from("hello"); // s1 拥有这个字符串
    let s2 = s1; // 所有权从 s1 移动到 s2
    
    // 现在 s1 无效了!
    // println!("{}", s1); // 编译错误!
    
    println!("{}", s2); // 正确!s2 是合法所有者
}

想象一下:你把房子钥匙给了别人(移动),你自己就没有钥匙了(不能访问)。

2.2.1.2 移动后原变量不可再使用

这是编译器强制保证的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fn main() {
    let v = vec![1, 2, 3]; // v 拥有这个向量
    let v2 = v; // 所有权移动到 v2
    
    // v 已经无效,以下代码都会编译错误:
    // println!("{:?}", v); // 错误!
    // v.push(4); // 错误!
    // let v3 = v; // 错误!
    
    println!("{:?}", v2); // 正常!v2 是合法所有者
}

2.2.2 移动的触发条件

2.2.2.1 赋值操作中的移动
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
fn main() {
    // 基本赋值
    let s1 = String::from("hello");
    let s2 = s1; // 移动!
    
    // println!("{}", s1); // 错误!s1 已无效
    println!("{}", s2); // 正确
    
    // 元组中的移动
    let t1 = (String::from("a"), String::from("b"));
    let t2 = t1; // 整个元组移动
    // println!("{:?}", t1); // 错误!
    println!("{:?}", t2); // 正确
    
    // 解构赋值中的移动
    let t3 = (String::from("x"), String::from("y"));
    let (a, b) = t3; // 解构时移动
    // println!("{:?}", t3); // 错误!
    println!("a = {}, b = {}", a, b); // 正确
}
2.2.2.2 函数参数传递中的移动
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fn main() {
    let s = String::from("hello");
    
    take_ownership(s); // s 的所有权移动到函数
    
    // println!("{}", s); // 错误!s 已无效
}

fn take_ownership(text: String) {
    println!("收到了: {}", text);
} // text 在这里被 drop,字符串被释放
2.2.2.3 函数返回值中的移动
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fn main() {
    let s = create_string(); // 返回值移动到 s
    
    println!("{}", s); // 正确
}

fn create_string() -> String {
    let s = String::from("hello");
    s // s 的所有权返回给调用者
} // 不需要 drop,因为所有权已经转移
2.2.2.4 模式匹配中的移动
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fn main() {
    let option: Option<String> = Some(String::from("hello"));
    
    match option {
        Some(s) => println!("收到了: {}", s),
        None => println!("没有值"),
    }
    
    // option 已经被移动到 match 中,不能再使用
    // println!("{:?}", option); // 错误!
}

2.2.3 堆上数据的移动

2.2.3.1 Box 的移动
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fn main() {
    let b1 = Box::new(String::from("hello"));
    let b2 = b1; // Box 的所有权移动
    
    // println!("{}", b1); // 错误!
    println!("{}", b2); // 正确
    
    // Box 是一种智能指针,解引用 *b 访问的是堆上的数据
    println!("解引用: {}", *b2); // 解引用: hello
}
2.2.3.2 Vec / String / HashMap 等集合的移动
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
fn main() {
    // Vec 的移动
    let v1 = vec![1, 2, 3];
    let v2 = v1; // v1 移动到 v2
    // println!("{:?}", v1); // 错误!
    println!("{:?}", v2); // 正确
    
    // String 的移动
    let s1 = String::from("hello");
    let s2 = s1; // s1 移动到 s2
    // println!("{}", s1); // 错误!
    println!("{}", s2); // 正确
    
    // HashMap 的移动
    use std::collections::HashMap;
    let mut m1 = HashMap::new();
    m1.insert("key", "value");
    let m2 = m1; // 移动
    // println!("{:?}", m1); // 错误!
    println!("{:?}", m2); // 正确
}
2.2.3.3 自定义类型(struct / enum)的移动行为
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
struct Person {
    name: String,
    age: u32,
}

fn main() {
    let p1 = Person {
        name: String::from("Alice"),
        age: 30,
    };
    let p2 = p1; // 整个结构体移动
    
    // p1 已经无效,访问 p1 的任何字段都会报错
    // println!("{}", p1.name); // 错误!
    println!("{}", p2.name); // 正确
    
    // 枚举的移动行为也一样
    enum Message {
        Text(String),
        Quit,
    }
    
    let msg1 = Message::Text(String::from("hello"));
    let msg2 = msg1; // 移动
    // if let Message::Text(s) = msg1 { } // 错误!
    if let Message::Text(s) = msg2 {
        println!("收到文本: {}", s); // 正确
    }
}
2.2.3.4 移动后底层数据重新分配
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
fn main() {
    // 移动操作只复制指针,不会重新分配堆数据
    let s1 = String::from("hello");
    let addr1 = s1.as_ptr() as usize; // 记录地址
    
    let s2 = s1;
    let addr2 = s2.as_ptr() as usize;
    
    // 两个字符串指向同一块堆内存
    println!("s1 地址: {:#x}", addr1); // s1 地址: 0x...
    println!("s2 地址: {:#x}", addr2); // s2 地址: 0x...(相同!)
    
    // 移动没有复制数据,只是复制了指针
}

2.2.4 栈上数据的复制(Copy Trait)

2.2.4.1 Copy trait 的定义

和移动相反,实现了 Copy trait 的类型会按位复制,原变量仍然有效:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fn main() {
    let x = 5;
    let y = x; // x 按位复制到 y,x 仍然有效!
    
    println!("x = {}, y = {}", x, y); // x = 5, y = 5
    
    // 整数类型默认实现了 Copy trait
    let a: i32 = 100;
    let b = a;
    println!("a = {}, b = {}", a, b); // a = 100, b = 100
}
2.2.4.2 哪些类型是 Copy
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
fn main() {
    // 基本类型都是 Copy
    let a: i8 = 1;
    let b = a; // 复制
    
    let a: i16 = 1;
    let b = a; // 复制
    
    let a: i32 = 1;
    let b = a; // 复制
    
    let a: i64 = 1;
    let b = a; // 复制
    
    let a: i128 = 1;
    let b = a; // 复制
    
    let a: isize = 1;
    let b = a; // 复制
    
    // 无符号整数
    let a: u8 = 1;
    let b = a; // 复制
    
    // 浮点数
    let a: f32 = 1.0;
    let b = a; // 复制
    
    let a: f64 = 1.0;
    let b = a; // 复制
    
    // 布尔
    let a: bool = true;
    let b = a; // 复制
    
    // 字符
    let a: char = 'a';
    let b = a; // 复制
    
    // 单元类型
    let a: () = ();
    let b = a; // 复制
    
    // 元组(如果所有元素都是 Copy)
    let a: (i32, i32) = (1, 2);
    let b = a; // 复制!因为 i32 是 Copy
    
    // 固定大小数组(如果元素都是 Copy)
    let a: [i32; 3] = [1, 2, 3];
    let b = a; // 复制!因为 i32 是 Copy
}
2.2.4.3 Copy 与 Clone 的关系
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 关系:实现了 Copy 的类型,必然也实现了 Clone
// (因为 Clone::clone() 对 Copy 类型来说就是按位复制,没有额外成本)
//
// 区别:Copy 不能自定义,永远是按位复制
//      Clone 可以自定义复制行为(如深复制)

fn main() {
    let s1 = String::from("hello");
    // let s2 = s1; // 移动,因为 String 不是 Copy
    let s2 = s1.clone(); // 克隆(深复制),显式操作
    println!("s1 = {}, s2 = {}", s1, s2); // 都有效!
}
2.2.4.4 所有字段都是 Copy 时结构体才是 Copy
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 所有字段都是 Copy,结构体也是 Copy!
struct Point {
    x: f64, // f64 是 Copy
    y: f64, // f64 是 Copy
}

fn main() {
    let p1 = Point { x: 1.0, y: 2.0 };
    let p2 = p1; // 复制!因为所有字段都是 Copy

    println!("p1.x = {}, p2.x = {}", p1.x, p2.x); // p1 和 p2 都有效!
}

// 但如果有一个字段不是 Copy,那整个结构体就不能是 Copy:
struct Person {
    name: String, // String 不是 Copy,是移动语义!
    age: u32,     // u32 是 Copy
}

fn demonstrate_person_move() {
    let p1 = Person {
        name: String::from("Alice"),
        age: 30,
    };
    let p2 = p1; // 移动!因为 name 不是 Copy

    // println!("{}", p1.name); // 编译错误!p1 已经无效了
    println!("{} 今年 {} 岁", p2.name, p2.age); // 正确!
}

fn main() {
    demonstrate_person_move();
}

2.2.5 移动后的使用限制

2.2.5.1 移动后变量不可再使用
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
fn main() {
    let v = vec![1, 2, 3];
    
    let v2 = v; // 移动
    
    // 编译错误:use of moved value: `v`
    // println!("{:?}", v);
    
    // 所有这些操作都会导致编译错误:
    // let v3 = v;
    // v.push(4);
    // v.len();
}
2.2.5.2 编译器错误示例分析
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
fn main() {
    let s = String::from("hello");
    let s2 = s;
    
    // 编译器会报错:
    // error[E0382]: use of moved value: `s`
    // println!("{}", s);
    
    // 错误信息:
    // "value borrowed here after move"
    // "help: consider cloning the value if it is expensive"
    // `s.clone()` 会解决这个问题,但有性能开销
    
    println!("{}", s2); // 这行没问题
}

2.2.6 移动与函数参数

2.2.6.1 按值传递与所有权转移
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
fn main() {
    let s = String::from("hello");
    
    call_with_move(s); // s 的所有权移动到函数
    
    // s 已经无效
    // println!("{}", s); // 编译错误!
}

fn call_with_move(text: String) {
    println!("收到了: {}", text);
} // text 在这里被 drop
2.2.6.2 借用作为替代方案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
fn main() {
    let s = String::from("hello");
    
    call_with_borrow(&s); // 只借用,不获取所有权
    
    // s 仍然有效!
    println!("调用后: {}", s); // 调用后: hello
}

fn call_with_borrow(text: &String) {
    println!("借来的字符串: {}", text);
} // text 是借用,不拥有数据,不负责 drop

2.2.7 返回值与所有权转移

2.2.7.1 函数返回值的所有权
1
2
3
4
5
6
7
8
9
fn main() {
    let s = create_string();
    println!("{}", s); // hello
}

fn create_string() -> String {
    let s = String::from("hello");
    s // 所有权返回给调用者
} // 函数结束时不会 drop,因为所有权已经转移
2.2.7.2 所有权模式

函数可以获取并返回所有权:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
fn take_and_return(text: String) -> String {
    // text 在这里获取所有权
    let processed = text.to_uppercase();
    processed // 所有权转移给调用者
}

fn main() {
    let original = String::from("hello");
    let result = take_and_return(original);
    // original 已经被移动到函数中
    
    println!("结果: {}", result); // 结果: HELLO
}
2.2.7.3 借用模式

更常见的做法是借用数据,不获取所有权:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fn calculate_length(text: &String) -> usize {
    text.len()
}

fn main() {
    let s = String::from("hello");
    let len = calculate_length(&s);
    
    println!("'{}' 的长度是 {}", s, len);
    // s 仍然有效!
}

mermaid

flowchart LR
    A[赋值/传参/返回] --> B{类型是 Copy?}
    B -->|是| C[复制<br/>原变量有效]
    B -->|否| D{类型是 Clone?}
    D -->|是| E[可以 clone]
    D -->|否| F[移动<br/>原变量无效]
    
    C --> G[按位复制]
    E --> H["深复制<br/>显式调用 .clone()"]
    F --> I[所有权转移<br/>原变量被消耗]
    
    style C fill:#90EE90
    style F fill:#FFB6C1
    style I fill:#FFB6C1

2.3 借用(Borrowing)

借用是 Rust 里最重要的概念之一!它让你可以在不获取所有权的情况下访问数据。想象一下:你去图书馆借书,书还是图书馆的,但你有了使用权。看完不还?那可是要被拉黑的——Rust 的借用检查器可比图书馆管理员严格多了。

2.3.1 引用的基本语法

2.3.1.1 引用符号:&
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fn main() {
    let s = String::from("hello");
    
    // 创建引用:用 & 符号
    let r = &s; // r 是 s 的引用,不获取所有权
    
    // 通过引用访问数据
    println!("引用指向的值: {}", r); // hello
    println!("原始值: {}", s); // s 仍然有效!
}
2.3.1.2 解引用符号:*

解引用就是通过引用访问它指向的数据:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
fn main() {
    let s = String::from("hello");
    let r = &s;
    
    // 解引用:用 * 符号
    println!("解引用: {}", *r); // hello
    
    // 对于实现了 Copy 的类型,解引用可以直接使用
    let x = 5;
    let r = &x;
    let y = *r; // 解引用得到 i32 值
    println!("x = {}, y = {}", x, y); // x = 5, y = 5
}
2.3.1.3 引用作为函数参数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
fn main() {
    let s = String::from("hello");
    
    // 传入引用
    print_string(&s);
    
    // s 仍然有效
    println!("main 中: {}", s); // main 中: hello
}

fn print_string(s: &String) {
    println!("print_string 中: {}", s); // print_string 中: hello
} // s 是引用,不拥有数据,不会触发 drop

2.3.2 不可变引用

2.3.2.1 &T 的创建

不可变引用允许你读取数据,但不能修改:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
fn main() {
    let s = String::from("hello");
    
    // 不可变引用:&T
    let r = &s;
    
    // 只能读取
    println!("读取: {}", r);
    println!("读取: {}", s);
    
    // 不能修改
    // r.push_str(" world"); // 编译错误!r 是不可变引用
}
2.3.2.2 不可变引用的特性

可以同时存在任意多个不可变引用!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
fn main() {
    let s = String::from("hello");
    
    // 多个不可变引用完全OK!
    let r1 = &s;
    let r2 = &s;
    let r3 = &s;
    
    println!("{}, {}, {}", r1, r2, r3);
    
    // 不可变引用可以在同一作用域存在多个
    // 因为没有人会修改数据,所以是安全的
}

2.3.3 可变引用

2.3.3.1 &mut T 的创建

可变引用允许你修改数据:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
fn main() {
    let mut s = String::from("hello");
    
    // 可变引用:&mut T
    let r = &mut s;
    
    // 可以修改
    r.push_str(", world");
    println!("修改后: {}", r); // 修改后: hello, world
    
    // 注意:s 本身也是可变的,所以 &mut s 是合法的
}
2.3.3.2 可变引用的特性

同时只能存在一个可变引用!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fn main() {
    let mut s = String::from("hello");
    
    let r1 = &mut s;
    // let r2 = &mut s; // 编译错误!不能同时存在两个可变引用
    
    r1.push_str(", world");
    println!("{}", r1); // hello, world
    
    // r1 在这里离开作用域,之后才能创建新的可变引用
}

这是为什么?因为多个可变引用可能导致数据竞争(data race)。想象一下你和你的室友同时往冰箱里塞东西——最后冰箱门可能都关不上,数据也是一样。

2.3.4 借用规则

2.3.4.1 借用规则

Rust 的借用规则总结如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
fn main() {
    // 规则1:你可以有任意多个不可变引用
    let s = String::from("hello");
    let r1 = &s;
    let r2 = &s;
    let r3 = &s;
    println!("{}, {}, {}", r1, r2, r3);
    
    // 规则2:或者(OR)一个可变引用,但不能同时存在!
    let mut s2 = String::from("hello");
    let r = &mut s2;
    r.push_str("!");
    // let r2 = &s2; // 错误!不能同时有可变引用和不可变引用
    
    // 规则3:引用必须始终有效(不能引用已释放的数据)
}

借用规则总结:

graph LR
    A[借用规则] --> B[任意多个不可变引用]
    A --> C[或一个可变引用]
    A --> D[但不能同时存在<br/>不可变和可变引用]
    
    B --> E[所有引用必须有效]
    C --> E
    D --> E
2.3.4.2 规则二:引用必须始终有效
1
2
3
4
5
6
7
8
9
fn main() {
    // 悬垂引用:引用了一个已经不存在的值
    // let r = dangle(); // 编译错误!
}

fn dangle() -> &String {
    let s = String::from("hello");
    &s // 返回 s 的引用,但 s 即将被销毁!
} // s 在这里被 drop,返回的引用无效
2.3.4.3 违反借用规则的编译错误分析
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fn main() {
    let mut s = String::from("hello");
    
    // 错误示例1:同时存在不可变和可变引用
    let r1 = &s; // 不可变引用
    let r2 = &mut s; // 可变引用!
    // 编译错误:cannot borrow `s` as mutable because it is also borrowed as immutable
    
    println!("{}, {}", r1, r2);
}
1
2
3
4
5
6
7
8
fn main() {
    let mut s = String::from("hello");
    
    // 错误示例2:在使用不可变引用之前修改数据
    let r = &s;
    s.push_str(", world"); // 编译错误!
    println!("{}", r);
}
1
2
3
4
5
6
7
8
fn main() {
    let mut s = String::from("hello");
    
    // 正确做法:可变引用用完再创建不可变引用
    s.push_str(", world"); // 先修改
    let r = &s; // 再借用
    println!("{}", r); // 最后使用
}

2.3.5 借用检查器(Borrow Checker)

2.3.5.1 借用检查器的工作原理

借用检查器是 Rust 编译器的一部分,负责检查所有借用是否合法。它基于生命周期(lifetimes)进行分析。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fn main() {
    let mut s = String::from("hello");
    
    let r = &s; // r 的生命周期从这里开始
    
    // 在 r 的生命周期内,不能修改 s
    // s.push_str(", world"); // 编译错误!
    
    println!("{}", r); // 最后一次使用 r
} // r 的生命周期在这里结束
2.3.5.2 NLL(Non-Lexical Lifetimes,Rust 2018)

NLL 是 Rust 2018 引入的重大改进。之前的借用检查器基于词法作用域(lexical scope),简单但保守;现在的 NLL 基于实际使用处,更聪明也更宽松。——说白了,编译器终于学会"用完就放手"这个道理了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// 旧版 Rust(词法作用域):
fn main() {
    let mut s = String::from("hello");
    
    let r = &s; // r 的作用域从这里开始
    
    if true {
        println!("{}", r); // r 仍然在使用
    }
    
    // r 的作用域到函数结束,但实际上只需要到这里
    s.push_str(", world"); // 旧版编译器报错,新版不报错!
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// Rust 2018+(NLL):
fn main() {
    let mut s = String::from("hello");
    
    let r = &s;
    println!("{}", r); // 最后一次使用 r
    
    // 从这里开始,r 的生命周期已经结束
    // 可以安全地修改 s
    s.push_str(", world"); // ✅ NLL 允许这样做!
    println!("{}", s);
}
2.3.5.3 NLL 的改进效果
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 之前的 Rust 版本无法编译这段代码:
fn main() {
    let mut v = vec![1, 2, 3];
    
    let first = &v[0]; // 借用 vec
    println!("第一个元素: {}", first);
    
    v.push(4); // 之前会报错,因为 first 的借用还没结束
    // NLL 之后:first 在 println! 后就不再使用了
    // 所以 push 是安全的
}

2.3.6 借用与函数参数

2.3.6.1 借用作为函数参数

借用传参是最常见的用法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
fn main() {
    let s = String::from("hello");
    
    // 传入不可变引用
    let len = get_length(&s);
    println!("'{}' 的长度是 {}", s, len); // s 仍然有效
    
    // 传入可变引用
    let mut s2 = String::from("hello");
    modify(&mut s2);
    println!("修改后: {}", s2); // 修改后: hello, world!
}

fn get_length(s: &String) -> usize {
    s.len()
}

fn modify(s: &mut String) {
    s.push_str(", world");
}
2.3.6.2 从函数返回引用

返回引用时,必须确保返回的引用是有效的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fn main() {
    let s = String::from("hello");
    let r = first_char(&s);
    println!("第一个字符: {}", r); // 第一个字符: h
}

// 正确:返回指向函数参数引用的引用
fn first_char(s: &String) -> &char {
    &s.chars().next().unwrap()
}
1
2
3
4
5
// 错误:返回指向局部变量的引用
// fn dangle() -> &String {
//     let s = String::from("hello");
//     &s // 错误!s 即将被销毁!
// }

mermaid

flowchart TD
    A[借用检查规则] --> B[不可变引用 &T]
    A --> C[可变引用 &mut T]
    
    B --> D[可同时存在多个]
    B --> E[只能读取数据]
    
    C --> F[最多同时存在一个]
    C --> G[可以修改数据]
    
    E --> H[安全读取]
    G --> I[修改需要唯一所有权]
    
    D --> J[并行读取没问题]
    F --> K[防止数据竞争]
    
    style D fill:#90EE90
    style F fill:#FFB6C1

2.4 生命周期(Lifetime)概述

生命周期是 Rust 最抽象的概念之一,但它其实是让 Rust 实现内存安全的关键!生命周期描述的是引用有效的作用域

2.4.1 什么是生命周期

2.4.1.1 生命周期的概念

每一个引用都有自己的"生命周期"——它是引用有效的代码区域:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fn main() {
    let r; // 声明一个引用,此时生命周期开始
    
    {
        let x = 5;
        r = &x; // r 引用 x
        println!("r: {}", r); // OK,x 仍然有效
    } // x 在这里被销毁
    
    // println!("r: {}", r); // 错误!r 引用了已销毁的 x!
}
2.4.1.2 生命周期与引用的有效性
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
fn main() {
    // 场景1:引用在作用域内始终有效
    let s = String::from("hello");
    let r = &s;
    println!("{}", r); // OK,s 仍然有效
    
    // 场景2:引用指向临时值(错误)
    // let r;
    // {
    //     let x = 5;
    //     r = &x;
    // }
    // println!("{}", r); // 错误!x 已经销毁!
}

2.4.2 生命周期标注语法

2.4.2.1 ‘a 语法

生命周期参数用单引号开头:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// 'a 读作 "tick a",是生命周期的名字
fn longest<'a>(s1: &'a str, s2: &'a str) -> &'a str {
    if s1.len() > s2.len() {
        s1
    } else {
        s2
    }
}

fn main() {
    let s1 = String::from("hello");
    let result;
    {
        let s2 = String::from("world");
        result = longest(&s1, &s2);
        println!("最长: {}", result); // OK
    }
    // println!("最长: {}", result); // 错误!s2 已经销毁
}
2.4.2.2 &‘a T 的含义
1
2
3
4
5
6
7
8
9
// &'a T 表示"生命周期为 'a 的 T 引用"
fn print(s: &'static str) {
    println!("{}", s);
}

fn main() {
    // 字符串字面量有 'static 生命周期
    print("hello"); // "hello" 的生命周期是 'static
}
2.4.2.3 多生命周期参数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
fn main() {
    // 多个生命周期参数
    fn multiple<'a, 'b>(s1: &'a str, s2: &'b str) -> &'a str {
        // 返回值的生命周期和 s1 相同
        s1
    }
    
    let s1 = String::from("hello");
    let s2 = String::from("world");
    let r = multiple(&s1, &s2);
    println!("{}", r);
}

2.4.3 生命周期省略规则(Elision,三条规则)

很多情况下,生命周期可以省略,因为编译器会自动推断。Rust 有三条生命周期省略规则:

2.4.3.1 输入生命周期省略规则
1
2
3
4
5
6
7
8
9
// 规则1:如果函数有参数引用,参数引用的生命周期成为输出生命周期
fn first_char(s: &str) -> &char {
    &s.chars().next().unwrap()
}

// 编译器推断后等价于:
fn first_char<'a>(s: &'a str) -> &'a char {
    &s.chars().next().unwrap()
}
2.4.3.2 输出生命周期省略规则
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 规则2:如果函数只有一个输入生命周期参数,它会被赋给所有输出生命周期
// (注意:这是简化说法,实际规则更复杂,但适用于大多数简单情况)
fn get_string(s: &String) -> &String {
    s // 返回的是输入参数本身,不是它的某个部分
}

// 编译器推断后等价于:
fn get_string<'a>(s: &'a String) -> &'a String {
    s
}
2.4.3.3 无法省略时的显式标注
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 当编译器无法推断时,必须显式标注
fn longest<'a>(x: &'a str, y: &str) -> &'a str {
    // 这里返回值的生命周期应该和 x 相同
    // 因为返回的可能是 x(我们只知道 x 的生命周期)
    if x.len() > y.len() { x } else { y }
}

// 无法推断的示例:
// fn ambiguous(x: &str, y: &str) -> &str { ... }
// 编译器不知道返回的是 x 还是 y,所以必须标注生命周期
1
2
3
4
5
6
7
8
// 常见编译错误 E0106:
// error[E0106]: missing lifetime specifier
// fn get_first(x: &str, y: &str) -> &str { ... }
//
// 修复:显式标注生命周期
fn get_first<'a>(x: &'a str, _y: &str) -> &'a str {
    x
}

2.4.4 结构体中的生命周期

2.4.4.1 struct &‘a str

当结构体持有引用时,必须标注生命周期:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// 结构体持有引用,必须标注生命周期
struct ImportantExcerpt<'a> {
    part: &'a str, // 这个引用的生命周期是 'a
}

fn main() {
    let novel = String::from("Call me Ishmael. Some years ago...");
    let first_sentence = novel.split('.').next().unwrap();
    
    let excerpt = ImportantExcerpt {
        part: first_sentence,
    };
    
    println!("{}", excerpt.part);
}
2.4.4.2 多引用字段的生命周期
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
struct MultiRefs<'a, 'b> {
    first: &'a str,
    second: &'b str,
}

fn main() {
    let s1 = String::from("hello");
    let s2 = String::from("world");
    
    let multi = MultiRefs {
        first: &s1,
        second: &s2,
    };
    
    println!("{} {}", multi.first, multi.second);
}

2.4.5 生命周期子类型

2.4.5.1 ‘a: ‘b

'a: 'b 表示"'a 至少和 'b 一样长",或者说"'a outlives 'b":

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// 'a: 'b 意味着 'a 比 'b 活得久
fn longest_valid<'a, 'b>(s1: &'a str, _s2: &'b str) -> &'a str
where
    'a: 'b, // 'a 必须 outlive 'b
{
    s1
}

fn main() {
    let s1 = String::from("hello");
    let result;
    {
        let s2 = String::from("world");
        // result 引用 s1,但 result 的生命周期需要和 s2 比较
        result = longest_valid(&s1, &s2);
    }
    // s2 已经销毁
    println!("{}", result); // OK,因为 s1 仍然有效
}
2.4.5.2 生命周期约束
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// 生命周期约束:'a: 'b 表示 'a outlives 'b
struct Context<'a, 'b> {
    name: &'a str,
    data: &'b str,
}

impl<'a, 'b> Context<'a, 'b> {
    fn new(name: &'a str, data: &'b str) -> Self {
        Context { name, data }
    }
    
    // 方法的生命周期约束
    fn get_name(&self) -> &'a str {
        self.name
    }
}

fn main() {
    let ctx = Context::new("Alice", "some data");
    println!("{}", ctx.get_name());
}

2.4.6 静态生命周期

2.4.6.1 ‘static 的含义

'static 生命周期意味着"程序整个运行期间都有效":

1
2
3
4
5
fn main() {
    // 字符串字面量有 'static 生命周期
    let s: &'static str = "我可以在程序任何地方使用";
    println!("{}", s);
}
2.4.6.2 字符串字面量的 ‘static 生命周期
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fn main() {
    // 字符串字面量存储在程序二进制中
    // 所以它们在整个程序运行期间都有效
    let s: &str = "hello";
    print_str(s); // 传入 'static 引用
}

fn print_str(s: &'static str) {
    println!("{}", s);
}
2.4.6.3 const / static 变量的隐含 ‘static
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// const 变量有 'static 生命周期
const PI: f64 = 3.14159;

// static 变量也有 'static 生命周期
static GREETING: &str = "Hello, World!";

fn main() {
    println!("PI: {}", PI);
    println!("GREETING: {}", GREETING);
}
2.4.6.4 T: ‘static 约束

T: 'static 表示"T 不包含任何非 ‘static 引用":

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// T: 'static 约束通常用于泛型
fn print_all<T: std::fmt::Display + 'static>(t: T) {
    println!("{}", t);
}

fn main() {
    // 整数是 'static
    print_all(42);
    
    // String 也是 'static(拥有所有权)
    print_all(String::from("hello"));
    
    // 但引用类型需要小心:
    // 如果引用指向非 'static 数据,就不能传给需要 'static 的函数
    let s = String::from("hello");
    // print_all(&s); // 错误!&String 的生命周期是 'a,不是 'static
}

mermaid

flowchart TD
    A[生命周期] --> B[生命周期标注 'a]
    A --> C[生命周期省略]
    A --> D['static 生命周期]
    
    B --> B1[输入生命周期]
    B --> B2[输出生命周期]
    B --> B3[结构体生命周期]
    
    C --> C1[规则1: 输入→输出]
    C --> C2[规则2: 单一输入]
    C --> C3[无法推断时需标注]
    
    D --> D1[程序整个运行期有效]
    D --> D2[字符串字面量]
    D --> D3[T: 'static 约束]

2.5 高级所有权主题

这一节我们深入探讨 Rust 所有权系统的高级特性:智能指针。所有权系统配合智能指针,让 Rust 能够优雅地处理复杂的内存管理场景。

2.5.1 Rc:单线程引用计数智能指针

当需要多个所有者时,Rc<T>(Reference Counted)就派上用场了!

2.5.1.1 Rc::new 创建引用计数
1
2
3
4
5
6
7
use std::rc::Rc;

fn main() {
    // 创建 Rc
    let rc = Rc::new(String::from("hello"));
    println!("初始引用计数: {}", Rc::strong_count(&rc)); // 1
}
2.5.1.2 Rc::clone 增加引用计数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
use std::rc::Rc;

fn main() {
    let rc1 = Rc::new(String::from("hello"));
    
    let rc2 = rc1.clone(); // 引用计数 +1
    let rc3 = rc1.clone(); // 引用计数 +1
    
    println!("rc1 引用计数: {}", Rc::strong_count(&rc1)); // 3
    println!("rc2 引用计数: {}", Rc::strong_count(&rc2)); // 3
    println!("rc3 引用计数: {}", Rc::strong_count(&rc3)); // 3
    
    // 所有 Rc 指向同一个数据
    println!("rc1: {}", rc1);
    println!("rc2: {}", rc2);
    println!("rc3: {}", rc3);
} // rc1, rc2, rc3 都离开作用域时,数据才被释放
2.5.1.3 Rc::downgrade 创建弱引用
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
use std::rc::{Rc, Weak};

fn main() {
    let strong = Rc::new(String::from("hello"));
    let weak: Weak<String> = Rc::downgrade(&strong);
    
    println!("强引用计数: {}", Rc::strong_count(&strong)); // 1
    println!("弱引用计数: {}", Rc::weak_count(&strong)); // 1
    
    // 升级弱引用为强引用
    if let Some(strong_ref) = weak.upgrade() {
        println!("升级成功: {}", strong_ref);
    }
}
2.5.1.4 Rc::strong_count / weak_count 查询计数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
use std::rc::Rc;

fn main() {
    let rc = Rc::new(vec![1, 2, 3]);
    
    println!("初始计数: {}", Rc::strong_count(&rc)); // 1
    
    {
        let rc2 = rc.clone();
        println!("clone 后计数: {}", Rc::strong_count(&rc)); // 2
    }
    
    println!("rc2 销毁后计数: {}", Rc::strong_count(&rc)); // 1
}
2.5.1.5 Rc 不是 Send + Sync
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
use std::rc::Rc;

// Rc 不是线程安全的!
// fn not_thread_safe(rc: Rc<i32>) {
//     // 在多线程中使用 Rc 会导致编译错误
// }

fn main() {
    let rc = Rc::new(42);
    // 只能用于单线程!
}

如果需要在多线程间共享所有权,使用 Arc<T>

2.5.2 Arc:原子引用计数(线程安全)

Arc<T>(Atomic Reference Counted)是 Rc<T> 的线程安全版本。

2.5.2.1 Arc::new / Arc::clone
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
use std::sync::Arc;

fn main() {
    let arc = Arc::new(vec![1, 2, 3]);
    
    let arc2 = arc.clone();
    let arc3 = arc.clone();
    
    println!("arc 计数: {}", Arc::strong_count(&arc)); // 3
    println!("arc2 计数: {}", Arc::strong_count(&arc2)); // 3
    println!("arc3 计数: {}", Arc::strong_count(&arc3)); // 3
}
2.5.2.2 Arc 的 Send + Sync 保证
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
use std::sync::Arc;
use std::thread;

fn main() {
    let data = Arc::new(vec![1, 2, 3]);
    
    // 可以在多线程间共享
    let handle = thread::spawn({
        let data = data.clone();
        move || {
            println!("线程中读取: {:?}", data);
        }
    });
    
    println!("主线程读取: {:?}", data);
    
    handle.join().unwrap();
}
2.5.2.3 Arc::make_mut(获取可变借用)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
use std::sync::Arc;

fn main() {
    let arc = Arc::new(vec![1, 2, 3]);
    
    // Arc::make_mut 返回 &mut T
    // 如果有多个 Arc 引用,会触发克隆(写时复制)
    let mutable_ref = Arc::make_mut(&mut arc.clone());
    mutable_ref.push(4);
    
    println!("修改后: {:?}", arc);
}
2.5.2.4 Arc::downgrade / Weak
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
use std::sync::{Arc, Weak};

fn main() {
    let strong = Arc::new(vec![1, 2, 3]);
    let weak: Weak<Vec<i32>> = Arc::downgrade(&strong);
    
    println!("强引用计数: {}", Arc::strong_count(&strong));
    println!("弱引用计数: {}", Arc::weak_count(&strong));
    
    // 升级弱引用
    if let Some(upgraded) = weak.upgrade() {
        println!("升级成功: {:?}", upgraded);
    }
}

2.5.3 RefCell:内部可变性(单线程)

RefCell<T> 提供了内部可变性——在不可变引用的地方提供可变访问。

2.5.3.1 RefCell 的设计目的
1
2
3
4
5
6
7
8
9
use std::cell::RefCell;

fn main() {
    let data = RefCell::new(5);
    
    // 通过 borrow() 获取不可变引用
    let r = data.borrow();
    println!("当前值: {}", r);
}
2.5.3.2 borrow() 返回 Ref
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
use std::cell::RefCell;

fn main() {
    let data = RefCell::new(vec![1, 2, 3]);
    
    // borrow() 返回 Ref<T>,类似于 &T
    let r = data.borrow();
    println!("借用: {:?}", r);
    
    // 可以有多个不可变借用
    let r2 = data.borrow();
    println!("同时借用: {:?}, {:?}", r, r2);
    
    // r 和 r2 离开作用域后,借用结束
}
2.5.3.3 borrow_mut() 返回 RefMut
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
use std::cell::RefCell;

fn main() {
    let data = RefCell::new(vec![1, 2, 3]);
    
    // borrow_mut() 返回 RefMut<T>,类似于 &mut T
    let mut r = data.borrow_mut();
    r.push(4);
    println!("修改后: {:?}", r);
}
2.5.3.4 借用冲突时 panic!
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
use std::cell::RefCell;

fn main() {
    let data = RefCell::new(5);
    
    let r1 = data.borrow(); // 不可变借用
    let r2 = data.borrow_mut(); // 可变借用冲突!
    // 运行时 panic: already borrowed: BorrowMutError
    
    println!("{}", r1); // 不会执行到这里
}

2.5.4 Cell:简单的内部可变性(单线程)

Cell<T> 是比 RefCell<T> 更轻量的选择,但只适用于 Copy 类型。

2.5.4.1 Cell 的设计目的
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
use std::cell::Cell;

fn main() {
    let data = Cell::new(5);

    // get() 获取值(按复制)
    println!("当前值: {}", data.get()); // 5

    // set() 设置值(按复制,因为 T 是 Copy)
    data.set(10);
    println!("设置后: {}", data.get()); // 10
}
2.5.4.2 Cell vs RefCell
特性CellRefCell
适用类型Copy 类型(简单值)任意类型(复杂结构也行)
获取引用不支持(没有 borrow())支持(borrow()/borrow_mut())
开销极低(只是简单复制)略高(运行时借用检查)
适用场景简单标量值的内部可变性需要借用的复杂数据
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
use std::cell::Cell;

fn main() {
    let counter = Cell::new(0);

    // 每次调用都增加计数器
    fn increment(c: &Cell<i32>) {
        c.set(c.get() + 1);
    }

    increment(&counter);
    increment(&counter);
    increment(&counter);

    println!("计数器: {}", counter.get()); // 3
}
2.5.4.3 RefCell vs Cell 怎么选
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
use std::cell::{Cell, RefCell};

// 如果你只需要修改 Copy 类型,用 Cell:
let count = Cell::new(0);
count.set(count.get() + 1);

// 如果你需要借用数据(引用),用 RefCell:
let text = RefCell::new(String::from("hello"));
text.borrow_mut().push_str(", world");
println!("{}", text.borrow());

// 如果你需要在多线程间共享,用 Arc<Mutex<T>> 或 Arc<RwLock<T>>

💡 小技巧:&Cell<T>&RefCell<T> 都实现了 Copy,所以可以轻易地在多个地方共享修改能力!

2.5.5 组合模式:Rc<RefCell> / Arc<Mutex> / Arc<RwLock>

组合使用这些智能指针可以应对各种复杂场景!

2.5.5.1 Rc<RefCell>:单线程多所有权可变数据
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
use std::cell::RefCell;
use std::rc::Rc;

fn main() {
    // Rc 允许多个所有者
    // RefCell 允许内部可变性
    let data = Rc::new(RefCell::new(vec![1, 2, 3]));
    
    // 克隆 Rc
    let data2 = data.clone();
    
    // 通过 RefCell 修改数据
    data.borrow_mut().push(4);
    
    println!("data1: {:?}", data.borrow());
    println!("data2: {:?}", data2.borrow());
}
2.5.5.2 Arc<Mutex>:多线程多所有权可变数据
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let data = Arc::new(Mutex::new(vec![1, 2, 3]));
    
    let handles: Vec<_> = (0..3).map(|i| {
        let data = data.clone();
        thread::spawn(move || {
            let mut d = data.lock().unwrap();
            d.push(i);
        })
    }).collect();
    
    for h in handles {
        h.join().unwrap();
    }
    
    println!("最终结果: {:?}", data.lock().unwrap());
}
2.5.5.3 Arc<RwLock>:多线程多所有权读共享写独占
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
use std::sync::{Arc, RwLock};
use std::thread;

fn main() {
    let data = Arc::new(RwLock::new(vec![1, 2, 3]));
    
    // 多个读线程
    let read_handles: Vec<_> = (0..3).map(|_| {
        let data = data.clone();
        thread::spawn(move || {
            let r = data.read().unwrap();
            println!("读取: {:?}", r);
        })
    }).collect();
    
    // 一个写线程
    let write_handle = {
        let data = data.clone();
        thread::spawn(move || {
            let mut w = data.write().unwrap();
            w.push(4);
        })
    };
    
    for h in read_handles {
        h.join().unwrap();
    }
    write_handle.join().unwrap();
    
    println!("最终: {:?}", data.read().unwrap());
}

2.5.6 Mutex 与 RwLock

2.5.6.1 Mutex:互斥锁
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
use std::sync::Mutex;

fn main() {
    let m = Mutex::new(5);
    
    {
        // lock() 返回 MutexGuard
        let mut num = m.lock().unwrap();
        *num = 6;
    } // MutexGuard 离开作用域,自动释放锁
    
    println!("修改后的值: {:?}", m);
}
2.5.6.2 MutexGuard 的作用域与 Drop
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
use std::sync::Mutex;

fn main() {
    let m = Mutex::new(0);
    
    // lock() 返回 Result<MutexGuard<T>, PoisonError>
    let guard = m.lock().unwrap();
    
    // MutexGuard 实现了 Drop,离开作用域时自动释放锁
    // 即使 panic,锁也会被释放(除非锁被 poison)
    
    println!("计数: {}", guard);
} // 锁在这里自动释放
2.5.6.3 RwLock:读写锁
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
use std::sync::RwLock;

fn main() {
    let lock = RwLock::new(5);
    
    // 读:read()
    {
        let r1 = lock.read().unwrap();
        let r2 = lock.read().unwrap();
        println!("读取: {}, {}", r1, r2); // 可以同时有多个读锁
    }
    
    // 写:write()
    {
        let mut w = lock.write().unwrap();
        *w = 10;
        // write() 是独占的
    }
    
    println!("最终值: {:?}", lock.read().unwrap());
}
2.5.6.4 read() / write() 方法
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
use std::sync::RwLock;

fn main() {
    let lock = RwLock::new(String::from("hello"));
    
    // 读操作
    {
        let r = lock.read().unwrap();
        println!("读取: {}", r);
    }
    
    // 写操作
    {
        let mut w = lock.write().unwrap();
        w.push_str(", world");
    }
    
    println!("最终: {:?}", lock.read().unwrap());
}

2.5.7 循环引用与内存泄漏

2.5.7.1 引用循环的形成
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use std::rc::Rc;
use std::cell::RefCell;

struct Node {
    value: i32,
    next: Option<Rc<RefCell<Node>>>,
    prev: Option<Rc<RefCell<Node>>>,
}

fn main() {
    let node1 = Rc::new(RefCell::new(Node {
        value: 1,
        next: None,
        prev: None,
    }));
    
    let node2 = Rc::new(RefCell::new(Node {
        value: 2,
        next: None,
        prev: None,
    }));
    
    // 创建循环引用
    node1.borrow_mut().next = Some(node2.clone());
    node2.borrow_mut().prev = Some(node1.clone());
    
    // 引用计数永远不会变成 0!
    // Rc::strong_count(&node1) == 2
    // Rc::strong_count(&node2) == 2
}
2.5.7.2 Weak 弱引用打破循环
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use std::rc::{Rc, Weak};
use std::cell::RefCell;

struct Node {
    value: i32,
    next: Option<Rc<RefCell<Node>>>,
    prev: Option<Weak<RefCell<Node>>>, // 改用 Weak
}

fn main() {
    let node1 = Rc::new(RefCell::new(Node {
        value: 1,
        next: None,
        prev: Weak::new(),
    }));
    
    let node2 = Rc::new(RefCell::new(Node {
        value: 2,
        next: None,
        prev: Weak::new(),
    }));
    
    // 不增加强引用计数!
    node1.borrow_mut().next = Some(node2.clone());
    node2.borrow_mut().prev = Rc::downgrade(&node1);
    
    // 引用计数:node1 = 1, node2 = 1
    println!("node1 强引用计数: {}", Rc::strong_count(&node1));
    println!("node1 弱引用计数: {}", Rc::weak_count(&node1));
}
2.5.7.3 用 Weak 替代强 Rc 环的一部分
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
use std::rc::{Rc, Weak};
use std::cell::RefCell;

struct TreeNode {
    value: i32,
    children: Vec<Rc<RefCell<TreeNode>>>,
    parent: Option<Weak<RefCell<TreeNode>>>, // 用 Weak 避免循环
}

fn main() {
    let leaf = Rc::new(RefCell::new(TreeNode {
        value: 3,
        children: vec![],
        parent: Weak::new(),
    }));
    
    let branch = Rc::new(RefCell::new(TreeNode {
        value: 2,
        children: vec![leaf.clone()],
        parent: Weak::new(),
    }));
    
    // leaf 指回 branch,但不增加 branch 的引用计数
    leaf.borrow_mut().parent = Some(Rc::downgrade(&branch));
    
    // 可以通过 parent 访问到 branch
    if let Some(parent) = leaf.borrow().parent.as_ref() {
        if let Some(p) = parent.upgrade() {
            println!("leaf 的父节点值: {}", p.borrow().value);
        }
    }
}
2.5.7.4 Rust 不保证无内存泄漏
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Rust 不能保证完全没有内存泄漏!
// 虽然编译器能防止大部分内存安全问题,但程序员仍然可以主动"泄漏"内存。
// (是的,Rust 给了你一把枪,但要不要对着自己的脚开枪,取决于你)

use std::rc::Rc;
use std::cell::RefCell;

fn main() {
    // 场景1:Rc 循环(内存泄漏)
    // struct A { b: Option<Rc<RefCell<B>>> }
    // struct B { a: Option<Rc<RefCell<A>>> }

    // 场景2:使用 Weak 打破循环(不会泄漏)
    // struct A { b: Option<Weak<RefCell<B>>> }
    // struct B { a: Option<Weak<RefCell<A>>> }

    // 场景3:手动泄漏 —— Box::leak!
    // Box::leak 将 Box 的内容泄漏到堆上,程序整个生命周期都不会释放它
    let leaked = Box::leak(Box::new(String::from("我永远不会释放")));
    println!("泄漏的字符串: {}", leaked); // 可以一直用,但永远不会被 drop

    // 这个手段在某些场景下很有用,比如:
    // - 创建一个程序启动时就创建、程序退出时才销毁的全局变量
    // - 将数据泄漏以获得 'static 生命周期
    // 但大多数时候,这是自找麻烦!除非你真的、真的、真的需要它。
}

2.5.8 悬垂引用(Dangling Reference)

2.5.8.1 悬垂引用的定义

悬垂引用是指引用了已释放内存的引用:

1
2
3
4
5
// C 语言的悬垂指针
char* dangle() {
    char s[] = "hello";
    return s; // 返回栈上局部变量的地址!
} // s 被销毁,指针指向无效内存
2.5.8.2 Rust 如何在编译期避免悬垂引用
1
2
3
4
5
6
7
8
9
// Rust 在编译时就禁止悬垂引用!
// 比 FBI 查水表还严格——在你犯错之前就把你拦下来
fn dangle() -> &String {
    let s = String::from("hello");
    &s // 编译错误!s 即将被销毁!想跑?门都没有!
}

// 编译器会报错:
// error[E0515]: returns a value referencing local variable `s`

mermaid

flowchart TD
    A[智能指针] --> B[Rc<T>]
    A --> C[Arc<T>]
    A --> D[RefCell<T>]
    A --> E[Mutex<T>]
    A --> F[Cell<T>]

    B --> B1[单线程]
    B --> B2[引用计数]
    B --> B3[不可变引用]

    C --> C1[多线程]
    C --> C2[原子引用计数]
    C --> C3[线程安全]

    D --> D1[内部可变性]
    D --> D2[运行时借用检查]
    D --> D3[单线程]

    E --> E1[互斥锁]
    E --> E2[线程安全可变访问]
    E --> E3[自动释放]

    F --> F1[轻量内部可变性]
    F --> F2[Copy 类型专用]
    F --> F3[无借用检查开销]

    B --> G[Rc<RefCell<T>>]
    C --> H[Arc<Mutex<T>>]

    G --> G1[多所有者 + 可变]
    H --> H1[多线程 + 可变]

2.6 Deref 与 DerefMut Trait(解引用多态)

DerefDerefMut 是 Rust 提供的两个 trait,它们让你的自定义类型可以像指针一样使用。这就是 Rust 零成本抽象的典型例子!

2.6.1 Deref trait

2.6.1.1 Deref::deref(self) -> &Self::Target
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use std::fmt::Display;

struct Wrapper<T> {
    value: T,
}

impl<T: Display> Display for Wrapper<T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.value)
    }
}

impl<T> Deref for Wrapper<T> {
    type Target = T; // 解引用后得到 T

    fn deref(&self) -> &T {
        &self.value
    }
}

fn main() {
    let wrapper = Wrapper { value: 42 };

    // 通过 Deref 自动解引用:Wrapper -> i32
    // println! 会自动解引用 wrapper 来匹配 Display trait
    println!("wrapper 通过解引用访问: {}", *wrapper); // 42
    println!("wrapper 通过 Display trait: {}", wrapper); // 42
}
2.6.1.2 自动解引用规则

Rust 会在方法调用时自动解引用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use std::fmt::Display;

struct Wrapper(String);

impl Deref for Wrapper {
    type Target = String;
    
    fn deref(&self) -> &String {
        &self.0
    }
}

fn print_length(s: &str) {
    println!("长度: {}", s.len());
}

fn main() {
    let wrapper = Wrapper(String::from("hello"));
    
    // 自动解引用:Wrapper -> String -> str
    print_length(&wrapper); // 正确!&Wrapper 自动变成 &str
    
    // 方法调用也自动解引用
    println!("{}", wrapper.to_uppercase()); // HELLO
}
2.6.1.3 Box 实现 Deref

Box<T> 是最典型的 Deref 实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
fn main() {
    let boxed = Box::new(String::from("hello"));
    
    // Box<String> 可以自动解引用为 &String
    let s: &String = &boxed;
    
    // 然后 &String 可以解引用为 &str
    let s2: &str = &boxed;
    
    println!("{}", s);
    println!("{}", s2);
}

2.6.2 DerefMut trait

2.6.2.1 DerefMut::deref_mut(&mut self) -> &mut Self::Target
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
struct MutWrapper<T> {
    value: T,
}

impl<T> Deref for MutWrapper<T> {
    type Target = T;
    
    fn deref(&self) -> &T {
        &self.value
    }
}

impl<T> DerefMut for MutWrapper<T> {
    fn deref_mut(&mut self) -> &mut T {
        &mut self.value
    }
}

fn main() {
    let mut wrapper = MutWrapper { value: 5 };
    
    // 可以通过解引用修改
    *wrapper = 10;
    println!("wrapper: {}", *wrapper); // wrapper: 10
}
2.6.2.2 DerefMut 与 Deref 的关系
1
2
3
4
5
6
7
8
9
// 要实现 DerefMut,必须先实现 Deref
// DerefMut 要求 &mut self 能解引用为 &mut Target
// 而 &mut T 同时也是 &T,所以 DerefMut 包含 Deref 的能力

impl<T> DerefMut for MutWrapper<T> {
    fn deref_mut(&mut self) -> &mut T {
        &mut self.value
    }
} // 隐含 Deref 的实现
2.6.2.3 自动解引用的可变规则
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
struct Counter {
    count: u32,
}

impl Counter {
    fn increment(&mut self) {
        self.count += 1;
    }
}

impl Deref for Counter {
    type Target = u32;
    
    fn deref(&self) -> &u32 {
        &self.count
    }
}

impl DerefMut for Counter {
    fn deref_mut(&mut self) -> &mut u32 {
        &mut self.count
    }
}

fn main() {
    let mut counter = Counter { count: 0 };
    
    // 自动解引用调用可变方法
    counter.increment();
    println!("count: {}", *counter); // count: 1
}

2.6.3 Deref 强制转换(Deref Coercion)

Deref 强制转换是 Rust 的语法糖,让类型可以自动转换:

2.6.3.1 &String → &str
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
fn takes_str(s: &str) {
    println!("{}", s);
}

fn main() {
    let s = String::from("hello");
    
    // &String 可以强制转换为 &str
    takes_str(&s);
    
    // 因为 String 实现了 Deref<Target = str>
}
2.6.3.2 &Vec → &[T]
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fn takes_slice(s: &[i32]) {
    println!("slice len: {}", s.len());
}

fn main() {
    let v = vec![1, 2, 3];
    
    // &Vec<T> 可以强制转换为 &[T]
    takes_slice(&v);
}
2.6.3.3 自定义类型的 Deref 强制转换
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
struct MyBox<T>(T);

impl<T> Deref for MyBox<T> {
    type Target = T;
    
    fn deref(&self) -> &T {
        &self.0
    }
}

fn main() {
    let box_str = MyBox(String::from("hello"));
    let box_i32 = MyBox(42);
    
    // 自定义类型也可以 Deref 强制转换
    let s: &String = &box_str; // MyBox<String> -> &String
    let i: &i32 = &box_i32; // MyBox<i32> -> &i32
    
    println!("s: {}, i: {}", s, i);
}

mermaid

flowchart TD
    A[Deref trait] --> B[解引用操作 *]
    A --> C[方法调用自动解引用]
    A --> D[强制类型转换]
    
    B --> B1[&T -> &U<br/>如果 T: Deref<Target = U>]
    
    C --> C1["obj.method()<br/>自动尝试 &obj, &mut obj, *obj"]
    
    D --> D1[&Box<String> -> &str]
    D --> D2["&Vec<T> -> &[T]"]
    D --> D3[自定义类型互转]
    
    style A fill:#ff6b6b
    style B fill:#51cf66
    style C fill:#51cf66
    style D fill:#51cf66

本章小结

恭喜你完成了第二章"所有权系统"的学习!🎉

在这一章中,我们深入探索了 Rust 最核心的概念:

  1. 所有权概述:了解了所有权三条规则、内存管理方式对比(手动管理 vs GC vs 所有权),以及 Rust 选择所有权模型的原因——零成本抽象、编译期安全、无运行时开销。

  2. 移动语义:掌握了移动的定义、触发条件、Copy trait,以及移动后变量的使用限制。

  3. 借用:深入理解了不可变引用和可变引用的规则,学会了使用 &&mut 创建引用,以及借用检查器的工作原理。

  4. 生命周期:学会了生命周期标注语法、生命周期省略规则,以及如何在结构体中标注生命周期。

  5. 高级所有权主题:掌握了 RcArcRefCellMutexRwLock 等智能指针的使用方法和组合模式。

  6. Deref 与 DerefMut:理解了 Rust 的解引用多态和 Deref 强制转换。

下一章预告: 第三章"复合数据类型"将带你学习 Rust 中的高级数据类型:字符串、元组、数组、结构体和枚举。这些是构建复杂程序的基础!准备好了吗?🚀

最后修改 April 20, 2026: 更新 Rust 教程 (c0c9eff)