← 返回文章列表
Rust 全指南:从入门到精通
基于费曼学习法、西蒙学习法、SQ3R 阅读法和康奈尔笔记法,系统掌握 Rust 编程语言的核心概念与实战技巧。
一、概览与提问(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 的所有权规则只有三条:
- Rust 中的每个值都有一个所有者(owner)。
- 同一时刻只能有一个所有者。
- 当所有者离开作用域时,值会被丢弃(drop)。
let s1 = String::from("hello");
let s2 = s1; // s1 的所有权转移(move)给了 s2
// println!("{s1}"); // 编译错误!s1 已经无效了
println!("{s2}"); // 正常工作为什么这样设计?因为 String 的数据存储在堆上。如果 s1 和 s2 都指向同一块堆内存,当它们同时离开作用域时,就会发生"双重释放"(double free)——同一块内存被释放两次,导致内存损坏。Rust 的解决方案简单而优雅:转移所有权后,原变量自动失效。
对于栈上的简单类型(如 i32、f64、bool、char),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 错误处理的精髓:如果 Result 是 Ok(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)),你必须在签收时检查。
常见误解澄清
- "Rust 太难了"——Rust 的学习曲线确实陡峭,但主要集中在前几周。一旦你建立起所有权思维,编译器就是最好的老师——它在编译期告诉你哪里有问题,而不是在凌晨三点的生产环境里。
- "Rust 和 C++ 一样底层"——Rust 确实可以做底层开发,但它的高级抽象(迭代器、闭包、模式匹配、Trait)让它写起来更像一门现代高级语言。零成本抽象意味着你不需要为这些便利付出运行时代价。
- "没有 GC 就意味着要手动管理内存"——恰恰相反。Rust 的所有权系统在编译期自动确定了每块内存的释放时机,你不需要手动调用
free或delete。它既不是 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,遮蔽允许类型改变const 与 let 的区别:常量用 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/f64、bool、char、元组(仅当所有元素都是 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 的所有权系统实现了编译期内存安全保证,其核心机制是:
-
所有权转移(Move):当值被赋值给新变量或传入函数时,所有权转移。原变量失效,编译器确保你不会使用已移动的值。这消除了双重释放(double free)的可能性。
-
借用规则:不可变借用
&T允许多个读取者,可变借用&mut T允许一个写入者,两者互斥。这个规则在编译期就消除了数据竞争——不需要运行时锁或原子操作来检测,编译器直接拒绝有潜在数据竞争的代码。 -
自动释放(Drop):当 owner 离开作用域,Rust 自动调用
Drop::drop()。这类似于 C++ 的 RAII(Resource Acquisition Is Initialization)模式,但 Rust 的编译器保证每个值只被 drop 一次。 -
生命周期检查:编译器通过生命周期标注(大部分自动推断)确保引用不会比它指向的数据活得更长。这消除了悬垂指针(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)**工作:
- 生命周期参数化:编译器为每个引用分配一个生命周期参数,表示该引用有效的代码区域。
- 约束求解:编译器收集所有约束(如"返回值的生命周期必须至少和输入引用一样长"),然后求解这些约束。
- 借用检查:编译器验证在任意程序点,引用的使用是否满足借用规则——不存在同时的可变和不可变借用、不存在悬垂引用。
- 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 块来执行以下五项操作:
- 解引用裸指针(raw pointer)
- 调用 unsafe 函数或方法
- 访问或修改可变静态变量
- 实现 unsafe trait
- 访问 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 封装。
与其他系统语言对比
| 特性 | Rust | C++ | Go | Zig |
|---|---|---|---|---|
| 内存安全 | 编译期保证 | 手动/智能指针 | 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 Clone | Copy:隐式浅拷贝(栈上简单类型)。Clone:显式深拷贝(clone() 方法)。实现了 Drop 的类型不能 Copy。 |
| Option<T> | Some(T) 或 None。替代 null。编译器强制处理 None 情况。常用方法:unwrap、unwrap_or、map、and_then、ok_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/Write | I/O trait | std::io::Read, std::io::Write |
本节总结
Rust 的知识体系围绕所有权系统展开。理解了所有权、借用和生命周期,就掌握了 Rust 最核心也最独特的部分。Trait 系统提供了灵活的多态和代码复用,Option/Result 实现了类型驱动的错误处理,泛型和迭代器带来了零成本的高层抽象。并发编程的安全保证直接源于所有权系统的借用规则。所有这些特性协同工作,构成了 Rust"在编译期消灭 bug"的承诺。
五、复习与实践(SQ3R · Recite & Review)
SQ3R 最后两步:复述关键要点,通过实践和复习巩固理解。
核心要点回顾
- 所有权是 Rust 的灵魂——每值一个 owner,转移后原变量失效,离开作用域自动 drop。
- 借用规则在编译期消除数据竞争:不可变引用可以多个,可变引用只能一个,两者互斥。
- 生命周期确保引用不会比它指向的数据活得更久,消除了悬垂指针。
Option<T>代替 null,Result<T, E>代替异常——编译器强制你处理错误情况。- Trait 定义共享行为,支持默认实现、Trait Bound、Blanket Implementation。
- 零成本抽象意味着高级特性(迭代器、泛型、闭包)编译后的性能等同于手写低级代码。
Send+Sync让编译器在编译期验证线程安全——"无畏并发"不是口号,是编译器强制的。
动手练习
- 基础练习:写一个函数,接收一个字符串 slice,返回其中最长的单词。如果字符串为空,返回
None。 - 所有权练习:实现一个简单的
StringBuilder,使用String的方法实现追加、插入、清空操作,注意所有权转移。 - Trait 练习:定义一个
Drawabletrait,为Circle和Rectangle实现它,然后写一个函数接收任何Drawable类型并调用其方法。 - 错误处理练习:使用
?操作符链式调用多个可能失败的文件操作,自定义错误类型并实现Fromtrait 进行错误转换。 - 并发练习:使用
Arc<Mutex<T>>实现一个简单的线程安全计数器,启动 10 个线程各增加 1000 次,验证最终结果为 10000。 - 迭代器练习:实现一个自定义迭代器
Fibonacci,生成斐波那契数列,使用.take(n).collect()获取前 n 个数。
常见陷阱
- 在循环中创建 String 而不重用——每次循环创建新
String会频繁分配/释放堆内存。考虑使用String::with_capacity()或在循环外创建并clear()重用。 - 过度使用
clone()——clone()会深拷贝数据。如果频繁 clone,可能意味着你的所有权设计需要重新思考。优先使用引用。 - 混淆
String和&str——String是拥有所有权的堆分配字符串,&str是字符串 slice(借用)。函数参数优先用&str,它更通用。 - 忽略
Result的错误——使用.unwrap()时要清楚该调用是否真的不可能失败。在库代码中用?传播错误,在应用代码中才考虑unwrap/expect。 - 生命周期标注恐惧——当编译器要求你标注生命周期时,不要随意加
'static。思考引用之间的实际关系,大多数情况只需要'a就够了。 - 在
RefCell运行时借用冲突——RefCell把借用检查推迟到运行时。如果你在持有不可变借用(borrow())时调用borrow_mut(),程序会 panic。
延伸阅读
- The Rust Programming Language(官方书籍)——本文的核心参考来源,Rust 学习的权威指南。
- Rust by Example——通过可运行的代码示例学习 Rust。
- Rust Reference——Rust 语言的详细技术参考文档。
- The Rustonomicon——深入 unsafe Rust 和底层细节。
- Tokio Tutorial——异步 Rust 运行时 Tokio 的官方教程。
- Rust Design Patterns——Rust 社区整理的设计模式。