一、概览与提问(SQ3R · Survey & Question)

SQ3R 第一步:快速浏览全貌,提出关键问题。

什么是 Rust?

Rust 是一门由 Mozilla 赞助发起、现由 Rust 基金会维护的系统级编程语言。它于 2015 年发布 1.0 版本,自那时起连续多年在 Stack Overflow 开发者调查中被评为"最受喜爱的编程语言"。Rust 的核心设计目标是三个看似矛盾的承诺:内存安全零成本抽象无畏并发——不需要垃圾回收器(GC),也不需要手动管理内存。

Rust 的哲学可以用一句话概括:在编译期消灭那些在其他语言中只能在运行时才暴露的 bug。它的所有权(Ownership)系统、借用检查器(Borrow Checker)和类型系统共同构成了一张安全网,让编译器在你运行代码之前就帮你找出潜在的内存错误和数据竞争。

核心问题

  • Rust 适合什么场景?——操作系统组件、WebAssembly、嵌入式开发、网络服务、CLI 工具、区块链基础设施、游戏引擎等对性能和安全同时有高要求的领域。
  • Rust 与 C/C++/Go 相比有什么优势?——相比 C/C++,Rust 在不牺牲性能的前提下保证了内存安全和线程安全;相比 Go,Rust 提供了更精细的内存控制和更高的运行时性能,零成本抽象意味着你不需要垃圾回收器的运行时开销。
  • 学习 Rust 最难的部分是什么?——所有权系统和生命周期(Lifetime)。这是 Rust 最独特的特性,也是初学者最常遇到编译错误的地方。但一旦理解,它们会从根本上改变你思考代码的方式。

技术全景图

Rust 的知识体系可以理解为五个核心层次:

第一层:基础语法——变量与可变性、数据类型(标量与复合)、函数、控制流(if/loop/while/for)、注释。

第二层:所有权系统——所有权规则(每值一个 owner、owner 离开作用域则 drop)、移动(Move)与克隆(Clone)、引用与借用(&/&mut)、Slice 类型。这是 Rust 的灵魂。

第三层:数据抽象——结构体(Struct)、枚举(Enum)与模式匹配(match/if let)、Trait 系统(类似接口但更强大)、泛型(Generics)。

第四层:错误处理与集合——Result<T, E>Option<T>panic!? 操作符、Vector、String、HashMap。

第五层:高级特性——生命周期(Lifetime)、智能指针(Box/Rc/Arc/RefCell)、闭包与迭代器、并发编程(线程/Channel/Mutex/Arc/Send/Sync)、async/await、宏(Macro)、unsafe Rust、模块系统。

二、用最简单的话说清楚(费曼学习法)

费曼学习法核心理念:如果你不能用简单的语言解释一件事,说明你还没有真正理解它。

核心概念讲解

所有权(Ownership)

想象你有一本书。你是这本书的"所有者"。如果你把书送给了别人,你就不再拥有它了——你不能再去读它,因为它已经不是你的了。Rust 中的所有权机制就是这样运作的。

Rust 的所有权规则只有三条:

  1. Rust 中的每个值都有一个所有者(owner)。
  2. 同一时刻只能有一个所有者。
  3. 当所有者离开作用域时,值会被丢弃(drop)。
let s1 = String::from("hello");
let s2 = s1; // s1 的所有权转移(move)给了 s2
// println!("{s1}"); // 编译错误!s1 已经无效了
println!("{s2}"); // 正常工作

为什么这样设计?因为 String 的数据存储在堆上。如果 s1s2 都指向同一块堆内存,当它们同时离开作用域时,就会发生"双重释放"(double free)——同一块内存被释放两次,导致内存损坏。Rust 的解决方案简单而优雅:转移所有权后,原变量自动失效。

对于栈上的简单类型(如 i32f64boolchar),Rust 会执行复制(Copy)而非移动,因为这些类型的复制成本极低:

let x = 5;
let y = x; // i32 实现了 Copy trait,所以 x 仍然有效
println!("x = {x}, y = {y}"); // 完全没问题

借用与引用(Borrowing & References)

如果你的朋友想看你的书,但你不想把书送给他,你可以"借"给他。他看完后还给你,但书的所有权始终是你的。这就是 Rust 中的"借用"。

fn calculate_length(s: &String) -> usize { // s 借用了 String
    s.len()
} // s 离开作用域,但因为它不拥有所有权,所以不会 drop
 
fn main() {
    let s1 = String::from("hello");
    let len = calculate_length(&s1); // 借用 s1
    println!("'{s1}' 的长度是 {len}。"); // s1 仍然有效
}

借用的核心规则:

  • 在同一时刻,你可以拥有任意数量的不可变引用(&T),或者恰好一个可变引用(&mut T),但不能同时拥有两者。
  • 引用必须始终有效(不允许悬垂引用,dangling reference)。
let mut s = String::from("hello");
 
let r1 = &s;     // 没问题
let r2 = &s;     // 没问题 — 多个不可变引用是允许的
// let r3 = &mut s; // 编译错误!不能在存在不可变引用时创建可变引用
println!("{r1}, {r2}");
 
let r3 = &mut s; // 没问题 — r1 和 r2 的最后一次使用已经结束
println!("{r3}");

这条规则看起来严格,但它的意义深远:它在编译期就消除了数据竞争(data race)的可能性。数据竞争需要同时满足三个条件才会发生:两个以上指针同时访问同一数据、其中至少一个在写入、没有同步机制。Rust 的借用规则直接从根源上打破了这三个条件。

生命周期(Lifetime)

生命周期是 Rust 用来追踪引用有效性的机制。每个引用都有一个生命周期,即引用保持有效的作用域。大多数时候生命周期是隐式推断的,但当编译器无法确定时,你需要显式标注。

// 告诉编译器:返回的引用至少和输入引用活得一样长
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}
 
fn main() {
    let string1 = String::from("long string is long");
    let result;
    {
        let string2 = String::from("xyz");
        result = longest(string1.as_str(), string2.as_str());
        println!("最长的字符串是: {result}");
    }
    // 如果在这里使用 result,编译器会报错,因为 string2 已经被 drop
}

生命周期的本质不是延长引用的寿命,而是让编译器验证引用不会活得比它指向的数据更久

枚举与模式匹配(Enum & Pattern Matching)

Rust 的枚举远比其他语言强大。每个枚举变体可以携带不同类型和数量的数据:

enum Message {
    Quit,                       // 不携带数据
    Move { x: i32, y: i32 },   // 像结构体一样的命名字段
    Write(String),              // 包含一个 String
    ChangeColor(i32, i32, i32), // 包含三个 i32
}
 
fn process(msg: Message) {
    match msg {
        Message::Quit => println!("退出"),
        Message::Move { x, y } => println!("移动到 ({x}, {y})"),
        Message::Write(text) => println!("写入: {text}"),
        Message::ChangeColor(r, g, b) => println!("颜色: ({r}, {g}, {b})"),
    }
}

match 会穷举所有可能的变体。如果你遗漏了任何一个变体,编译器会报错。这意味着你永远不会忘记处理某种情况。

Option<T>——Rust 没有空值(null),而是用 Option<T> 表示"可能有值,也可能没有":

let some_number: Option<i32> = Some(42);
let no_number: Option<i32> = None;
 
// 你不能直接把 Option<i32> 当作 i32 使用
// let sum = some_number + 5; // 编译错误!必须先处理 None 的情况
 
match some_number {
    Some(n) => println!("值是: {n}"),
    None => println!("没有值"),
}

Tony Hoare——null 的发明者——称之为"十亿美元的错误"。Rust 用类型系统把这个问题从运行时搬到了编译时。

错误处理:Result 与 ?

Rust 将错误分为两类:可恢复错误(如文件不存在)和不可恢复错误(如数组越界)。

  • 不可恢复错误使用 panic! 宏,程序直接崩溃。
  • 可恢复错误使用 Result<T, E> 类型:
use std::fs::File;
use std::io::{self, Read};
 
fn read_username_from_file() -> Result<String, io::Error> {
    let mut username = String::new();
    File::open("username.txt")?.read_to_string(&mut username)?;
    Ok(username)
}

? 操作符是 Rust 错误处理的精髓:如果 ResultOk(v),它返回 v;如果是 Err(e),它立即从当前函数返回 Err(e)。这让错误处理既显式又简洁——你始终能看到哪些操作可能失败,但不需要写大量的 match 来处理。

Trait 系统

Trait 定义了某种共享行为——类似于其他语言中的接口(interface),但更强大:

pub trait Summary {
    fn summarize_author(&self) -> String;
 
    // 默认实现可以调用其他 trait 方法
    fn summarize(&self) -> String {
        format!("(阅读更多来自 {}...)", self.summarize_author())
    }
}
 
pub struct Article {
    pub title: String,
    pub author: String,
}
 
impl Summary for Article {
    fn summarize_author(&self) -> String {
        format!("@{}", self.author)
    }
    // summarize 使用默认实现
}

Trait 还可以用作函数参数和返回类型:

// impl Trait 语法 — 接受任何实现了 Summary 的类型
pub fn notify(item: &impl Summary) {
    println!("突发新闻!{}", item.summarize());
}
 
// Trait Bound 语法 — 更灵活的约束形式
pub fn notify_bound<T: Summary>(item: &T) {
    println!("突发新闻!{}", item.summarize());
}
 
// where 子句 — 当约束复杂时更清晰
fn some_function<T, U>(t: &T, u: &U) -> i32
where
    T: std::fmt::Display + Clone,
    U: Clone + std::fmt::Debug,
{
    unimplemented!()
}

Trait 的一个重要限制是孤儿规则(Orphan Rule):你只能在 trait 或类型至少有一个是本地(你自己的 crate)的情况下,为类型实现 trait。这防止了不同 crate 之间的实现冲突。

类比与比喻

  • 所有权 = 房产证。一个房子同一时间只有一个房主。你把房子卖了(move),你就不再是房主了。
  • 借用 = 借书。书的所有权没变,你只是暂时持有它。你可以读(不可变借用),也可以在书上做笔记(可变借用),但不能同时借给两个人做笔记。
  • 生命周期 = 租约。你的租约不能比房子的存在时间更长。编译器就是那个检查租约的房东。
  • Option<T> = 安全的盲盒。你要么打开盒子拿到里面的东西(Some(T)),要么盒子是空的(None),但你不能假装盒子里一定有东西。
  • Result<T, E> = 快递。快递要么成功送达(Ok(T)),要么附带一张错误通知单(Err(E)),你必须在签收时检查。

常见误解澄清

  1. "Rust 太难了"——Rust 的学习曲线确实陡峭,但主要集中在前几周。一旦你建立起所有权思维,编译器就是最好的老师——它在编译期告诉你哪里有问题,而不是在凌晨三点的生产环境里。
  2. "Rust 和 C++ 一样底层"——Rust 确实可以做底层开发,但它的高级抽象(迭代器、闭包、模式匹配、Trait)让它写起来更像一门现代高级语言。零成本抽象意味着你不需要为这些便利付出运行时代价。
  3. "没有 GC 就意味着要手动管理内存"——恰恰相反。Rust 的所有权系统在编译期自动确定了每块内存的释放时机,你不需要手动调用 freedelete。它既不是 GC,也不是手动管理,而是第三条路。

三、锥形深入(西蒙学习法)

西蒙学习法:集中精力,像锥子一样在一个方向上持续深入。

第一层:核心基础

变量与可变性

Rust 中变量默认不可变。这是有意为之的设计选择:不可变性让代码更容易推理,也配合所有权系统工作。如果需要可变,显式声明:

let x = 5;          // 不可变
// x = 6;           // 编译错误
let mut y = 5;      // 可变
y = 6;              // 没问题
 
let z = 5;          // 变量遮蔽(shadowing)
let z = z + 1;      // 创建了一个新变量,不是修改原来的
let z = z * 2;      // 可以改变类型
let spaces = "   ";
let spaces = spaces.len(); // 从 &str 变成了 usize,遮蔽允许类型改变

constlet 的区别:常量用 const 声明,必须标注类型,不能使用 mut,也不能在运行时计算:

const MAX_POINTS: u32 = 100_000;

数据类型

标量类型:

// 整数:i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, isize, usize
let a: i32 = -42;          // 有符号 32 位
let b: u8 = 255;           // 无符号 8 位
let c = 98_222;            // 数字可读性分隔符
let d = 0xff;              // 十六进制
let e = 0o77;              // 八进制
let f = 0b1111_0000;       // 二进制
let g = b'A';              // 字节(u8)
 
// 浮点数
let pi: f64 = 3.14159;     // f64 是默认,现代 CPU 上速度与 f32 接近
 
// 布尔
let t: bool = true;
 
// 字符 — Unicode 标量值
let heart = '❤';
let z = 'ℤ';

复合类型:

// 元组 — 固定长度,可以有不同类型
let tup: (i32, f64, bool) = (500, 6.4, true);
let (x, y, z) = tup;       // 解构
let first = tup.0;         // 索引访问
 
// 数组 — 固定长度,相同类型
let arr: [i32; 5] = [1, 2, 3, 4, 5];
let first = arr[0];
let repeat = [3; 5];       // [3, 3, 3, 3, 3]

函数

fn add(x: i32, y: i32) -> i32 {
    x + y // 最后一个表达式作为返回值(没有分号)
    // x + y; // 如果加分号,变成语句,返回 (),编译错误
}
 
fn print_sum(x: i32, y: i32) {
    println!("总和: {}", x + y); // 没有返回值,返回 ()
}
 
// 语句(statement)不返回值,表达式(expression)返回值
let y = {
    let x = 3;
    x + 1 // 表达式,注意没有分号
};

控制流

// if 是表达式,可以赋值
let condition = true;
let number = if condition { 5 } else { 6 };
 
// loop — 无限循环,可用 break 返回值
let mut counter = 0;
let result = loop {
    counter += 1;
    if counter == 10 {
        break counter * 2; // loop 表达式返回 20
    }
};
 
// 循环标签 — 在嵌套循环中指定 break/continue 目标
'outer: for i in 0..5 {
    for j in 0..5 {
        if i == 2 && j == 2 {
            break 'outer;
        }
    }
}
 
// while
let mut n = 3;
while n != 0 {
    println!("{n}!");
    n -= 1;
}
 
// for — Rust 中最常用的循环
let arr = [10, 20, 30, 40, 50];
for element in arr.iter() {
    println!("值为: {element}");
}
for number in (1..4).rev() { // Range: 3, 2, 1
    println!("{number}!");
}

所有权系统(深入)

所有权系统的设计核心是管理堆上的数据。栈上的数据因为大小固定、生命周期可预测,由编译器自动管理。堆上的数据则需要明确的所有者来决定何时释放。

Move 语义的详细规则:

// String 的内存布局:栈上存储 (ptr, len, capacity),堆上存储实际数据
let s1 = String::from("hello");
let s2 = s1; // 浅拷贝栈上的 (ptr, len, capacity),然后 s1 失效
// s1 被 move 了,不能再用
 
// clone — 深拷贝
let s3 = String::from("world");
let s4 = s3.clone();
println!("{s3}, {s4}"); // 两者都有效
 
// 函数传参也会 move
let s = String::from("hello");
takes_ownership(s);
// println!("{s}"); // 编译错误:s 的值已被移动
 
let x = 5;
makes_copy(x);
println!("{x}"); // 没问题:i32 实现了 Copy
 
// 函数返回值也会转移所有权
let s1 = gives_ownership(); // 函数返回值移动到 s1
 
fn takes_ownership(some_string: String) {
    println!("{some_string}");
} // some_string 离开作用域,drop 被调用
 
fn makes_copy(some_integer: i32) {
    println!("{some_integer}");
}
 
fn gives_ownership() -> String {
    String::from("yours")
}

**实现了 Copy trait 的类型:**所有整数类型、f32/f64boolchar、元组(仅当所有元素都是 Copy 时,如 (i32, i32)Copy,但 (i32, String) 不是)。

引用与借用(深入)

// 不可变借用 — 可以有多个
let s = String::from("hello");
let r1 = &s;
let r2 = &s;
println!("{r1} and {r2}"); // 多个不可变借用同时存在,没问题
 
// 可变借用 — 同一时刻只能有一个
let mut s = String::from("hello");
let r1 = &mut s;
r1.push_str(", world");
// let r2 = &mut s; // 编译错误:不能同时有两个可变借用
println!("{r1}");
 
// 不可变与可变不能同时存在
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
println!("{r1} and {r2}"); // r1 和 r2 最后一次使用在这里
let r3 = &mut s; // 没问题:r1 和 r2 不再使用
println!("{r3}");

引用的作用域从引入开始,到最后一次使用结束(Non-Lexical Lifetimes, NLT)。这意味着即使引用在语法上还没离开大括号,只要编译器确认它不再被使用,它就算"结束"了。

Slice 类型

Slice 是对集合中一段连续元素的引用:

let s = String::from("hello world");
 
// 字符串 slice
let hello = &s[0..5];   // "hello"
let world = &s[6..11];  // "world"
let whole = &s[..];     // "hello world"
let from_start = &s[..5];  // "hello"
let to_end = &s[6..];     // "world"
 
// 实用函数:返回第一个单词
fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }
    &s[..]
}

注意函数签名用的是 &str 而不是 &String——&str 是字符串 slice,可以同时接受 &String(通过 Deref 强制转换)和 &str,更通用。

结构体(Struct)

// 常规结构体
struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}
 
// 创建实例
let mut user1 = User {
    active: true,
    username: String::from("alice"),
    email: String::from("alice@example.com"),
    sign_in_count: 1,
};
 
// 结构体更新语法 — 从另一个实例借用值
let user2 = User {
    email: String::from("bob@example.com"),
    ..user1 // 剩余字段从 user1 移动过来(user1 的 String 字段此后不可用)
};
 
// 元组结构体 — 有名字的元组
struct Color(i32, i32, i32);
let black = Color(0, 0, 0);
 
// 单元结构体 — 没有任何字段
struct AlwaysEqual;
 
// 方法
impl User {
    // &self 借用实例
    fn summary(&self) -> String {
        format!("{} ({})", self.username, self.email)
    }
 
    // 关联函数 — 不接收 self,像"构造函数"
    fn new(username: String, email: String) -> User {
        User {
            active: true,
            username,
            email,
            sign_in_count: 0,
        }
    }
}

枚举与模式匹配(深入)

enum WebEvent {
    PageLoad,
    KeyPress(char),
    Paste(String),
    Click { x: i64, y: i64 },
}
 
fn inspect(event: WebEvent) {
    match event {
        WebEvent::PageLoad => println!("页面加载"),
        WebEvent::KeyPress(c) => println!("按键: '{c}'"),
        WebEvent::Paste(s) => println!("粘贴: \"{s}\""),
        WebEvent::Click { x, y } => println!("点击位置: ({x}, {y})"),
    }
}
 
// if let — 只关心一个变体时使用
let config_max = Some(3u8);
if let Some(max) = config_max {
    println!("最大值是 {max}");
}
 
// let else — Rust 1.65+ 引入
fn get_count(item: &Item) -> usize {
    let Item::Count(count) = item else {
        return 0;
    };
    count
}

错误处理(Result / Option 深入)

use std::fs::File;
use std::io::{self, Read};
 
// Result<T, E> 的定义
// enum Result<T, E> {
//     Ok(T),
//     Err(E),
// }
 
// 基本用法
let f = File::open("hello.txt");
let f = match f {
    Ok(file) => file,
    Err(error) => match error.kind() {
        io::ErrorKind::NotFound => {
            File::create("hello.txt").unwrap_or_else(|error| {
                panic!("创建文件失败: {error:?}");
            })
        }
        other_error => {
            panic!("打开文件失败: {other_error:?}");
        }
    },
};
 
// unwrap 和 expect — 原型开发时快速处理
// let f = File::open("hello.txt").unwrap(); // 失败时 panic
// let f = File::open("hello.txt").expect("无法打开 hello.txt"); // 自定义错误信息
 
// ? 操作符链式调用
fn read_file_contents(path: &str) -> Result<String, io::Error> {
    let mut contents = String::new();
    File::open(path)?.read_to_string(&mut contents)?;
    Ok(contents)
}
 
// Option 的常用方法
let some_value: Option<i32> = Some(42);
some_value.unwrap();          // 42 — None 时 panic
some_value.unwrap_or(0);     // 42 — None 时返回 0
some_value.unwrap_or_default(); // 42 — None 时返回默认值
some_value.expect("必须有值"); // 42 — None 时 panic 并显示消息
some_value.map(|x| x * 2);   // Some(84)
some_value.and_then(|x| Some(x + 1)); // Some(43)
some_value.filter(|&x| x > 40); // Some(42)
some_value.ok_or("没有值");   // Ok(42) — 转成 Result

第二层:进阶用法

泛型(Generics)

// 泛型函数
fn largest<T: PartialOrd>(list: &[T]) -> &T {
    let mut largest = &list[0];
    for item in &list[1..] {
        if item > largest {
            largest = item;
        }
    }
    largest
}
 
// 泛型结构体
struct Point<T> {
    x: T,
    y: T,
}
 
// 泛型可以有不同的类型参数
struct Point2D<T, U> {
    x: T,
    y: U,
}
 
// 为泛型实现方法
impl<T: std::fmt::Display> Point<T> {
    fn to_string(&self) -> String {
        format!("({}, {})", self.x, self.y)
    }
}
 
// 为具体类型实现方法
impl Point<f32> {
    fn distance_from_origin(&self) -> f32 {
        (self.x.powi(2) + self.y.powi(2)).sqrt()
    }
}

Trait 与 Trait Bound(深入)

use std::fmt::{Display, Debug};
 
// 定义 Trait
pub trait Describe {
    fn describe_author(&self) -> String;
 
    fn describe(&self) -> String {
        format!("由 {} 创建", self.describe_author())
    }
}
 
// 实现 Trait
struct Article {
    author: String,
    title: String,
}
 
impl Describe for Article {
    fn describe_author(&self) -> String {
        self.author.clone()
    }
}
 
// Trait 作为参数
fn print_description(item: &impl Describe) {
    println!("{}", item.describe());
}
 
// 等价的 trait bound 写法
fn print_description_bound<T: Describe>(item: &T) {
    println!("{}", item.describe());
}
 
// 多个 trait bound
fn print_debug_and_display(item: &(impl Debug + Display)) {
    println!("Debug: {:?}", item);
    println!("Display: {}", item);
}
 
// where 子句
fn compare_and_print<T, U>(t: &T, u: &U)
where
    T: Display + PartialOrd,
    U: Display,
{
    println!("t = {t}, u = {u}");
}
 
// 条件实现(Blanket Implementation)
// 标准库中的例子:所有实现了 Display 的类型自动获得 ToString
// impl<T: Display> ToString for T { ... }
let s = 42.to_string(); // i32 实现了 Display,所以有 to_string

生命周期深入

// 生命周期标注语法:'a 是生命周期参数
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}
 
// 结构体中的生命周期
struct ImportantExcerpt<'a> {
    part: &'a str,
}
 
fn main() {
    let novel = String::from("从前有座山。山里有座庙...");
    let first_sentence = novel.split('。').next().expect("找不到'。'");
    let excerpt = ImportantExcerpt {
        part: first_sentence,
    };
}
 
// 生命周期省略规则(编译器替你推断的三条规则)
// 1. 每个引用参数获得自己的生命周期参数
// 2. 如果只有一个输入生命周期,它赋给所有输出
// 3. 如果有多个输入但有 &self 或 &mut self,self 的生命周期赋给输出
 
// 静态生命周期 'static — 整个程序运行期间都有效
let s: &'static str = "我是静态字符串";
 
// 生命周期与泛型一起使用
fn longest_with_announcement<'a, T>(
    x: &'a str,
    y: &'a str,
    ann: T,
) -> &'a str
where
    T: Display,
{
    println!("公告: {ann}");
    if x.len() > y.len() { x } else { y }
}

智能指针

use std::boxed::Box;
use std::rc::Rc;
use std::sync::Arc;
use std::cell::RefCell;
use std::sync::Mutex;
 
// Box<T> — 堆分配,单一所有者
let b = Box::new(5);
println!("b = {b}");
 
// Box 的典型用途:递归类型
enum List {
    Cons(i32, Box<List>),
    Nil,
}
use List::{Cons, Nil};
let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
 
// Rc<T> — 引用计数,多所有权(仅单线程)
use std::rc::Rc;
let a = Rc::new(5);
let b = Rc::clone(&a); // 增加引用计数,不深拷贝
let c = Rc::clone(&a);
println!("引用计数: {}", Rc::strong_count(&a)); // 3
 
// RefCell<T> — 内部可变性,在运行时检查借用规则
let value = Rc::new(RefCell::new(5));
let a = Rc::clone(&value);
*borrow_mut = 6;
println!("value = {value:?}");
 
// Arc<T> — 原子引用计数,线程安全的多所有权
let data = Arc::new(Mutex::new(vec![1, 2, 3]));

并发编程

use std::thread;
use std::sync::{mpsc, Arc, Mutex};
use std::time::Duration;
 
// 创建线程
let handle = thread::spawn(|| {
    for i in 1..10 {
        println!("子线程数字: {i}");
        thread::sleep(Duration::from_millis(1));
    }
});
 
for i in 1..5 {
    println!("主线程数字: {i}");
    thread::sleep(Duration::from_millis(1));
}
handle.join().unwrap();
 
// 使用 move 闭包捕获所有权
let v = vec![1, 2, 3];
let handle = thread::spawn(move || {
    println!("向量: {v:?}");
});
 
// 消息传递(Channel)
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
    let vals = vec![
        String::from("你好"),
        String::from("来自"),
        String::from("子线程"),
    ];
    for val in vals {
        tx.send(val).unwrap();
    }
});
for received in rx {
    println!("收到: {received}");
}
 
// 共享状态并发 — Mutex + Arc
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
 
for _ in 0..10 {
    let counter = Arc::clone(&counter);
    let handle = thread::spawn(move || {
        let mut num = counter.lock().unwrap();
        *num += 1;
    });
    handles.push(handle);
}
for handle in handles {
    handle.join().unwrap();
}
println!("结果: {}", *counter.lock().unwrap()); // 10
 
// Send 和 Sync Trait
// Send — 类型可以在线程间转移所有权(大多数类型自动实现)
// Sync — 类型可以在线程间共享引用(&T 可以安全跨线程)
// Rc<T> 不是 Send 也不是 Sync,所以不能跨线程
// Arc<T> 是 Send + Sync

闭包与迭代器

// 闭包 — 匿名函数,可以捕获环境变量
let plus_one = |x: i32| x + 1;
println!("{}", plus_one(5)); // 6
 
// 捕获环境变量
let x = 4;
let equal_to_x = |z| z == x; // 不可变借用 x
println!("{}", equal_to_x(4)); // true
 
// move 闭包 — 强制获取所有权
let x = vec![1, 2, 3];
let equal_to_x = move |z| z == x; // x 的所有权被移入闭包
 
// 迭代器
let v = vec![1, 2, 3, 4, 5];
 
// 惰性求值 — 迭代器适配器不会立即执行
let v2: Vec<i32> = v.iter()
    .map(|x| x + 1)         // 每个元素 +1
    .filter(|x| *x > 3)     // 过滤大于 3 的
    .collect();              // 消费迭代器,收集结果
 
// 常用迭代器方法
let sum: i32 = v.iter().sum();                     // 求和
let has_even = v.iter().any(|&x| x % 2 == 0);      // 是否有偶数
let all_positive = v.iter().all(|&x| x > 0);        // 是否全部正数
let first_even = v.iter().find(|&&x| x % 2 == 0);   // 找第一个偶数
 
// 自定义迭代器
struct Counter {
    count: u32,
}
 
impl Counter {
    fn new() -> Counter {
        Counter { count: 0 }
    }
}
 
impl Iterator for Counter {
    type Item = u32;
 
    fn next(&mut self) -> Option<Self::Item> {
        if self.count < 5 {
            self.count += 1;
            Some(self.count)
        } else {
            None
        }
    }
}

宏(Macro)

// declarative macro — 用模式匹配定义
macro_rules! say_hello {
    () => {
        println!("你好!");
    };
    ($name:expr) => {
        println!("你好, {}!", $name);
    };
}
 
say_hello!();           // 你好!
say_hello!("Rust");     // 你好, Rust!
 
// vec! 宏的简化实现
macro_rules! my_vec {
    ($($x:expr),*) => {
        {
            let mut temp_vec = Vec::new();
            $(temp_vec.push($x);)*
            temp_vec
        }
    };
}
 
let v = my_vec![1, 2, 3];

模块系统

// 模块声明
mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
        fn seat_at_table() {} // 私有(默认)
    }
 
    mod serving {
        fn take_order() {}
        fn serve_order() {}
        fn take_payment() {}
    }
}
 
// 使用 use 引入路径
use crate::front_of_house::hosting; // 绝对路径
use self::front_of_house::hosting;  // 相对路径
 
// use as 重命名
use std::fmt::Result as FmtResult;
use std::io::Result as IoResult;
 
// 使用 pub use 重导出
pub use crate::front_of_house::hosting;
 
// 将模块拆分到不同文件
// src/lib.rs:
// mod front_of_house; // 告诉编译器去找 src/front_of_house.rs

第三层:深度解析

所有权系统的内存安全保证原理

Rust 的所有权系统实现了编译期内存安全保证,其核心机制是:

  1. 所有权转移(Move):当值被赋值给新变量或传入函数时,所有权转移。原变量失效,编译器确保你不会使用已移动的值。这消除了双重释放(double free)的可能性。

  2. 借用规则:不可变借用 &T 允许多个读取者,可变借用 &mut T 允许一个写入者,两者互斥。这个规则在编译期就消除了数据竞争——不需要运行时锁或原子操作来检测,编译器直接拒绝有潜在数据竞争的代码。

  3. 自动释放(Drop):当 owner 离开作用域,Rust 自动调用 Drop::drop()。这类似于 C++ 的 RAII(Resource Acquisition Is Initialization)模式,但 Rust 的编译器保证每个值只被 drop 一次。

  4. 生命周期检查:编译器通过生命周期标注(大部分自动推断)确保引用不会比它指向的数据活得更长。这消除了悬垂指针(dangling pointer)和 use-after-free 错误。

这些检查全部在编译期完成,运行时零额外开销。编译后的代码就像手写的 C 代码一样高效——甚至有时更高效,因为编译器有更多的静态信息可以优化。

零成本抽象

"零成本抽象"是 Rust 的核心设计原则之一,源自 C++:你不需要为你没有使用的功能付出代价。更具体地说,Rust 的高级抽象(泛型、Trait、迭代器、闭包、async/await)在编译后生成的代码,与你手写的低级代码一样高效。

// 迭代器的零成本抽象示例
fn sum_iterator(data: &[i32]) -> i32 {
    data.iter().sum()
}
 
fn sum_manual(data: &[i32]) -> i32 {
    let mut total = 0;
    for &item in data {
        total += item;
    }
    total
}
// 编译后两者的机器码几乎完全相同

泛型通过**单态化(monomorphization)**实现零成本:编译器在编译期为每个具体类型生成一份专门的代码,而不是像 Java 那样在运行时做类型擦除和虚方法分派。

借用检查器内部机制

借用检查器是 Rust 编译器中最复杂的组件之一。它基于**非词法生命周期(Non-Lexical Lifetimes, NLL)**工作:

  1. 生命周期参数化:编译器为每个引用分配一个生命周期参数,表示该引用有效的代码区域。
  2. 约束求解:编译器收集所有约束(如"返回值的生命周期必须至少和输入引用一样长"),然后求解这些约束。
  3. 借用检查:编译器验证在任意程序点,引用的使用是否满足借用规则——不存在同时的可变和不可变借用、不存在悬垂引用。
  4. NLL 优化:引用的生命周期不是简单地到作用域结束,而是到最后一次使用的位置。这让很多之前无法编译的代码变得合法。

async/await 与异步运行时

Rust 的异步模型与其他语言不同:语言本身只提供 async/await 语法和 Future trait,不包含运行时。你需要选择一个异步运行时(如 Tokio):

use tokio::time::{sleep, Duration};
 
async fn fetch_data(id: u32) -> String {
    sleep(Duration::from_millis(100)).await;
    format!("数据 {id}")
}
 
async fn process() {
    // 并发执行多个 future
    let (a, b) = tokio::join!(
        fetch_data(1),
        fetch_data(2),
    );
    println!("结果: {a}, {b}");
}
 
#[tokio::main]
async fn main() {
    process().await;
}

Future 是一个惰性的状态机:它不会自动执行,必须被 .await 或运行时轮询。这种设计让异步代码的内存开销可预测(每个 future 的大小在编译期确定),而不需要像 Go goroutine 那样动态分配栈。

unsafe Rust 与 FFI

当 Rust 的安全保证太严格时,你可以使用 unsafe 块来执行以下五项操作:

  1. 解引用裸指针(raw pointer)
  2. 调用 unsafe 函数或方法
  3. 访问或修改可变静态变量
  4. 实现 unsafe trait
  5. 访问 union 的字段
unsafe fn dangerous() {}
 
unsafe {
    dangerous();
}
 
// FFI — 调用 C 函数
extern "C" {
    fn abs(input: i32) -> i32;
}
 
fn main() {
    let x = unsafe { abs(-5) };
    println!("{x}");
}

unsafe 不是"关闭安全检查"的开关,而是把安全保证的责任从编译器转移给了程序员unsafe 块应该尽可能小,并用安全 API 封装。

与其他系统语言对比

特性RustC++GoZig
内存安全编译期保证手动/智能指针GC手动
并发安全编译期检查手动同步CSP 模型手动
运行时开销几乎为零零(可选 RTTI/异常)GC 开销极小
泛型/多态泛型+Trait模板+虚函数泛型(受限)编译期泛型
错误处理Result/Option异常/错误码error 接口错误联合
构建系统Cargo(内置)CMake/Make 等go build(内置)zig build(内置)

四、要点笔记(康奈尔笔记法)

康奈尔笔记法:左侧写关键词线索,右侧写详细笔记,底部写总结。

关键概念速查表

线索/关键词详细笔记
所有权规则每值一个 owner;同时只能一个 owner;owner 离开作用域则 drop。转移所有权后原变量失效(move 语义)。
借用规则&T 不可变引用可多个;&mut T 可变引用只能一个;两者不能同时存在。编译期检查,零运行时开销。
生命周期描述引用有效的作用域。大多数自动推断(省略规则)。结构体持有引用时需要显式标注。'static 表示整个程序运行期有效。
Copy vs CloneCopy:隐式浅拷贝(栈上简单类型)。Clone:显式深拷贝(clone() 方法)。实现了 Drop 的类型不能 Copy
Option<T>Some(T)None。替代 null。编译器强制处理 None 情况。常用方法:unwrapunwrap_ormapand_thenok_or
Result<T, E>Ok(T)Err(E)? 操作符简化错误传播。unwrap/expect 用于原型开发。
模式匹配match 穷举所有变体;if let 处理单个变体;let else 提供默认处理。
Trait定义共享行为。默认实现、Trait Bound(impl Trait/where)、Blanket Implementation。孤儿规则防止实现冲突。
泛型参数化类型。编译期单态化——为每个具体类型生成专门代码,零运行时开销。
智能指针Box<T> 堆分配;Rc<T> 单线程引用计数;Arc<T> 线程安全引用计数;RefCell<T> 运行时借用检查。
并发线程(std::thread)、Channel(mpsc)、Mutex + Arc。Send + Sync trait 保证线程安全。
闭包匿名函数,捕获环境变量。Fn(不可变借用)、FnMut(可变借用)、FnOnce(获取所有权)。
迭代器惰性求值。Iterator trait 只需实现 next()。适配器(map/filter)和消费者(collect/sum)组合。

核心 API / Trait 速查

类型 / Trait用途示例
String可增长 UTF-8 字符串String::from("hello"), s.push_str(" world")
Vec<T>可增长数组vec![1, 2, 3], v.push(4), v.iter()
HashMap<K, V>键值映射HashMap::new(), map.insert(k, v), map.get(&k)
Option<T>可能为空的值Some(v), None, .unwrap_or(default)
Result<T, E>可恢复错误Ok(v), Err(e), ? 操作符
Box<T>堆分配指针Box::new(value), 递归类型
Rc<T>单线程引用计数Rc::new(v), Rc::clone(&rc)
Arc<T>线程安全引用计数Arc::new(v), Arc::clone(&arc)
RefCell<T>运行时借用检查RefCell::new(v), .borrow(), .borrow_mut()
Mutex<T>互斥锁Mutex::new(v), .lock().unwrap()
Iterator迭代器 trait.next(), .map(), .filter(), .collect()
Display格式化输出 {}impl fmt::Display for T
Debug调试输出 {:?}#[derive(Debug)]
Clone显式深拷贝.clone()
Copy隐式复制整数、浮点、布尔、char
Drop自定义清理impl Drop for T
Send可跨线程转移大多数类型自动实现
Sync可跨线程共享Arc<T> 要求 T: Send + Sync
From/Into类型转换impl From<T> for U, .into()
Read/WriteI/O traitstd::io::Read, std::io::Write

本节总结

Rust 的知识体系围绕所有权系统展开。理解了所有权、借用和生命周期,就掌握了 Rust 最核心也最独特的部分。Trait 系统提供了灵活的多态和代码复用,Option/Result 实现了类型驱动的错误处理,泛型和迭代器带来了零成本的高层抽象。并发编程的安全保证直接源于所有权系统的借用规则。所有这些特性协同工作,构成了 Rust"在编译期消灭 bug"的承诺。

五、复习与实践(SQ3R · Recite & Review)

SQ3R 最后两步:复述关键要点,通过实践和复习巩固理解。

核心要点回顾

  1. 所有权是 Rust 的灵魂——每值一个 owner,转移后原变量失效,离开作用域自动 drop。
  2. 借用规则在编译期消除数据竞争:不可变引用可以多个,可变引用只能一个,两者互斥。
  3. 生命周期确保引用不会比它指向的数据活得更久,消除了悬垂指针。
  4. Option<T> 代替 null,Result<T, E> 代替异常——编译器强制你处理错误情况。
  5. Trait 定义共享行为,支持默认实现、Trait Bound、Blanket Implementation。
  6. 零成本抽象意味着高级特性(迭代器、泛型、闭包)编译后的性能等同于手写低级代码。
  7. Send + Sync 让编译器在编译期验证线程安全——"无畏并发"不是口号,是编译器强制的。

动手练习

  1. 基础练习:写一个函数,接收一个字符串 slice,返回其中最长的单词。如果字符串为空,返回 None
  2. 所有权练习:实现一个简单的 StringBuilder,使用 String 的方法实现追加、插入、清空操作,注意所有权转移。
  3. Trait 练习:定义一个 Drawable trait,为 CircleRectangle 实现它,然后写一个函数接收任何 Drawable 类型并调用其方法。
  4. 错误处理练习:使用 ? 操作符链式调用多个可能失败的文件操作,自定义错误类型并实现 From trait 进行错误转换。
  5. 并发练习:使用 Arc<Mutex<T>> 实现一个简单的线程安全计数器,启动 10 个线程各增加 1000 次,验证最终结果为 10000。
  6. 迭代器练习:实现一个自定义迭代器 Fibonacci,生成斐波那契数列,使用 .take(n).collect() 获取前 n 个数。

常见陷阱

  1. 在循环中创建 String 而不重用——每次循环创建新 String 会频繁分配/释放堆内存。考虑使用 String::with_capacity() 或在循环外创建并 clear() 重用。
  2. 过度使用 clone()——clone() 会深拷贝数据。如果频繁 clone,可能意味着你的所有权设计需要重新思考。优先使用引用。
  3. 混淆 String&str——String 是拥有所有权的堆分配字符串,&str 是字符串 slice(借用)。函数参数优先用 &str,它更通用。
  4. 忽略 Result 的错误——使用 .unwrap() 时要清楚该调用是否真的不可能失败。在库代码中用 ? 传播错误,在应用代码中才考虑 unwrap/expect
  5. 生命周期标注恐惧——当编译器要求你标注生命周期时,不要随意加 'static。思考引用之间的实际关系,大多数情况只需要 'a 就够了。
  6. RefCell 运行时借用冲突——RefCell 把借用检查推迟到运行时。如果你在持有不可变借用(borrow())时调用 borrow_mut(),程序会 panic。

延伸阅读