📚 Rust 课程系列

  1. 课程概览
  2. 基础语法
  3. 函数与输入输出
  4. 所有权、借用与生命周期
  5. 结构体、枚举与模式匹配
  6. 类型系统:泛型、trait 与多态
  7. 集合与容器
  8. 错误处理与 Panic 恢复
  9. 模块、属性与宏
  10. 智能指针、迭代器与闭包(本文)
  11. 并发与异步编程
  12. Unsafe Rust 与常用 trait 详解
  13. 工具链、Cargo 与外部 crate
  14. 最佳实践、性能与调试

智能指针提供超越引用的所有权语义,迭代器是 Rust 零成本抽象的典范,闭包则是配合迭代器的匿名函数。本章把三者放在一起:Box/Rc/Arc/Cell/RefCell 解决所有权与内部可变性,迭代器与闭包则支撑函数式的数据流水线。深入内容可参考文末延伸阅读。

智能指针

Rust 的"智能指针"不仅是指针,还携带所有权语义与运行时行为。核心思路:编译时借用规则(&/&mut)优先;当它们无法满足需求时,才引入智能指针

💡 提示:Rust 的智能指针与 C++ 的设计哲学不同——C++ 依赖运行时行为(如 shared_ptr 的引用计数),而 Rust 优先在编译期保证安全,只在必要时才退到运行时检查(如 RefCell)。

🔄 对比Box<T> ≈ C++ unique_ptr<T>(独占所有权),Rc<T> ≈ C++ shared_ptr<T>(共享所有权),但 Rust 编译器在编译期强制执行所有权规则,而非依赖程序员自律。

速查表

智能指针所有权可变性线程安全典型用途
Box<T>独占外部可变✅ (若 T: Send+Sync)递归数据结构、堆上大对象
Rc<T>共享(引用计数)需配合 RefCell单线程多所有者(树/图)
Arc<T>共享(原子引用计数)需配合 Mutex/RwLock多线程共享只读数据
Cell<T>独占内部可变(按值)小型 Copy 类型的标志位/缓存
RefCell<T>独占内部可变(运行时借用检查)单线程需绕开编译时借用规则
Weak<T>不持有所有权取决于 Rc/Arc打破循环引用

Box<T>

将值放到堆上,最简单的智能指针。核心用途:递归数据结构(编译时大小未知)和堆上大对象。

1
2
let b = Box::new(5);
let boxed_array: Box<[i32]> = Box::new([1, 2, 3]);

💡 提示Box 满足编译器对"大小已知"的要求——递归类型(如 enum List { Cons(i32, Box<List>), Nil })必须用 Box 包裹递归分支,否则编译器无法确定大小。

⚠️ 注意Box 是零开销抽象——解引用 *b 与栈上访问性能一致,唯一的额外开销是一次堆分配。

Rc<T> 与 Arc<T>

引用计数智能指针,允许多个所有者共享同一数据。Rc 用于单线程,Arc 用于多线程(原子计数,开销略高)。

1
2
3
4
5
use std::rc::Rc;

let a = Rc::new(String::from("hello"));
let b = Rc::clone(&a); // 增加引用计数,不复制数据
println!("count = {}", Rc::strong_count(&a)); // 2
1
2
3
4
use std::sync::Arc;
// Arc: 多线程共享——clone 是原子操作,略慢于 Rc
let data = Arc::new(vec![1, 2, 3]);
let d = Arc::clone(&data); // 传给线程即可,Arc 保证线程安全

💡 提示Rc::clone 只增加计数(O(1)),不深拷贝数据。Arc 的 clone 使用原子操作,比 Rc 慢约 2-3 倍,但仍是 O(1)。

⚠️ 注意Rc<T> 不是 Send/Sync——跨线程传递会在编译期报错。多线程场景必须用 Arc<T>

⚠️ 注意:循环引用会导致引用计数永远无法归零,造成内存泄漏。需要父指针时使用 Weak<T> 断开循环:

1
2
3
4
use std::rc::{Rc, Weak};
use std::cell::RefCell;

struct Node { parent: RefCell<Weak<Node>>, children: RefCell<Vec<Rc<Node>>> }

🔄 对比Rc/Arc ≈ C++ shared_ptr,但 Rust 的循环引用问题同样存在。C++ 用 weak_ptr 打破循环,Rust 用 Weak<T>——机制相同,但 Rust 的类型系统会在编译期阻止你把 Rc 传到其他线程。

Cell<T> 与 RefCell<T>(内部可变性)

内部可变性(Interior Mutability)是 Rust 的设计模式:在外部 &T(不可变引用)下修改内部数据。这是对编译时借用规则的有条件绕开。

💡 提示:为什么需要内部可变性?有些场景下,从调用者角度看数据"逻辑上不可变",但内部实现需要修改(如缓存、引用计数、惰性初始化)。编译时借用规则无法区分"逻辑不可变"和"物理不可变",所以需要运行时机制。

  • Cell<T>:按值存取,适合 Copy 类型。通过 get/set/replace 操作,不提供引用借用。
1
2
3
4
use std::cell::Cell;
let c = Cell::new(5);
c.set(10);
let v = c.get(); // Copy 出来
  • RefCell<T>:运行时借用检查。borrow() 返回 Ref<T>borrow_mut() 返回 RefMut<T>。违反借用规则时 panic
1
2
3
4
5
6
7
8
9
use std::rc::Rc;
use std::cell::RefCell;

#[derive(Debug)]
struct Node { value: i32, children: Vec<Rc<RefCell<Node>>> }

let leaf = Rc::new(RefCell::new(Node { value: 3, children: vec![] }));
let branch = Rc::new(RefCell::new(Node { value: 5, children: vec![Rc::clone(&leaf)] }));
leaf.borrow_mut().value = 10; // 通过 RefCell 修改,即使 leaf 被 Rc 共享

⚠️ 注意RefCellborrow_mut() 与已有的 borrow() 同时存在时会 panic,而非编译错误。这是"将错误从编译期推迟到运行期"的权衡——务必在逻辑上保证不会同时持有可变与不可变借用。

💡 提示Rc<RefCell<T>> 是单线程中"多所有者 + 可变访问"的经典组合。多线程对应物是 Arc<Mutex<T>>(详见第 10 章)。

Cow 与 Pin(进阶)

🔬 进阶:以下内容在特定场景下有用,初学者可跳过。

Cow<B>(写时克隆):借用或拥有的抽象——当数据无需修改时零开销借用,需要修改时才克隆。

1
2
3
4
5
6
7
8
9
use std::borrow::Cow;

fn remove_spaces<'a>(input: &'a str) -> Cow<'a, str> {
if input.contains(' ') {
Cow::Owned(input.replace(' ', "")) // 需要修改 → 拥有副本
} else {
Cow::Borrowed(input) // 无需修改 → 零开销借用
}
}

🔄 对比Cow ≈ C++ 的 copy-on-write 惯用法,但 Rust 在类型层面区分了借用与拥有两种状态。

Pin<P> 与 Unpin:确保值在内存中不被移动,主要用于 async 生成的自引用 Future。大多数类型自动实现 Unpin(可安全移动),只有自引用类型需要 Pin 约束。

1
2
3
use std::pin::Pin;
let mut val = 5;
let pinned = Pin::new(&mut val); // 普通类型无需 Pin,这里仅演示 API

⚠️ 注意:日常编码几乎不需要手动使用 Pin——async 运行时(Tokio 等)会自动处理。只有实现自引用类型时才需深入理解。

智能指针选择指南

  1. 优先编译时借用规则&/&mut)——零开销,编译期保证安全
  2. 需要堆分配或递归类型 → Box<T>
  3. 单线程多所有者 → Rc<T>;多线程 → Arc<T>
  4. 需要内部可变性:Copy 类型 → Cell<T>,非 CopyRefCell<T>
  5. 多线程可变共享 → Arc<Mutex<T>>Arc<RwLock<T>>
  6. 循环引用 → Weak<T> 打破

💡 提示:更详细的对比、与 C++ 智能指针的对照、混合使用指南,见 Rust 智能指针

迭代器与闭包

迭代器(Iterator)

Rust 的 for 循环本质是迭代器语法糖。任何实现 Iterator trait 的类型都可遍历——只需定义 Item 类型和 next() 方法。

1
2
3
let v = vec![1, 2, 3];
let mut iter = v.iter(); // 产生 &i32
assert_eq!(iter.next(), Some(&1));

💡 提示iter() 产生 &Titer_mut() 产生 &mut Tinto_iter() 消费集合产生 T——三者对应借用、可变借用、获取所有权,与 Rust 所有权模型完全对齐。

常用适配器(惰性,返回新迭代器):

适配器作用示例
map转换每个元素.map(|x| x * 2)
filter保留满足条件的元素.filter(|&x| x > 0)
enumerate添加索引.enumerate()(usize, &T)
zip合并两个迭代器.zip(other_iter)
take / skip取前 n 个 / 跳过前 n 个.take(5)
chain连接两个迭代器.chain(other_iter)

消费者(触发实际计算):

1
2
3
4
let v = vec![1, 2, 3, 4, 5];
let sum: i32 = v.iter().sum(); // 求和
let has_large = v.iter().any(|&x| x > 3); // 任一满足
let all_pos = v.iter().all(|&x| x > 0); // 全部满足

💡 提示:迭代器是惰性的——map/filter 不会执行任何计算,直到被消费者(如 collect/sum/for 等)驱动。这意味着迭代器链是零成本抽象:编译后等价于手写循环。

⚠️ 注意:忘记消费迭代器是常见错误——写了 .map(...) 却没有 .collect()for,代码编译通过但什么都不做。编译器会发出"unused must_use"警告,务必留意。

自定义迭代器:实现 Iterator trait 的 Itemnext() 即可。

1
2
3
4
5
6
7
8
struct Counter { count: u32 }
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 }
}
}
// now: for i in Counter { count: 0 } { ... }

闭包(Closure)

匿名函数,可捕获环境变量,语法 |param| body

1
2
3
let factor = 2;
let multiply = |x| x * factor; // 捕获 factor 的引用
assert_eq!(multiply(5), 10);

闭包与迭代器搭配——链式数据处理的惯用写法:

1
2
3
4
let v: Vec<i32> = vec![1, 2, 3]
.into_iter()
.map(|x| x * x)
.collect();

闭包捕获 trait:闭包根据捕获方式自动实现以下 trait,决定可被调用的方式:

Trait捕获方式可调用次数典型场景
Fn不可变借用 &T多次只读访问环境
FnMut可变借用 &mut T多次修改环境变量
FnOnce获取所有权 T一次消费环境变量(如 move 闭包传入线程)
1
2
3
4
5
6
7
8
fn apply<F>(f: F) -> i32
where F: Fn(i32) -> i32 {
f(5) // Fn 可多次调用
}

let x = 10;
let add_x = |y| y + x; // 实现 Fn(只借用 x)
println!("{}", apply(add_x)); // 15

💡 提示:Rust 编译器按"最小权限"原则自动推断闭包 trait——如果只需 &T 就不会 &mut T,如果只需 &mut T 就不会 move。这避免了不必要的限制。

⚠️ 注意:当闭包需要跨越作用域(如传入 thread::spawn)时,环境变量必须被 move 而非借用。使用 move 关键字强制移动捕获:let c = move |x| x * factor;

🔄 对比:Rust 闭包的 Fn/FnMut/FnOnce 三级 trait 比 C++ 的按值/引用捕获更精细——编译器自动选择最小权限,而非依赖程序员手动指定捕获列表。Go 的闭包则始终按引用捕获,可能导致意外的数据竞争。

💡 提示:更详细的迭代器适配器与消费者方法、自定义迭代器、性能优化,见 Rust 迭代器


延伸阅读:

  • Rust 智能指针Box/Rc/Arc/RefCell/Cell 的对比、与 C++ 智能指针的对照、混合使用与选择指南)。
  • Rust 迭代器map/filter/fold/zip 等适配器与消费者方法的逐一详解、自定义迭代器、性能优化)。