智能指针、迭代器与闭包
📚 Rust 课程系列
智能指针提供超越引用的所有权语义,迭代器是 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 | |
💡 提示:
Box满足编译器对"大小已知"的要求——递归类型(如enum List { Cons(i32, Box<List>), Nil })必须用Box包裹递归分支,否则编译器无法确定大小。
⚠️ 注意:
Box是零开销抽象——解引用*b与栈上访问性能一致,唯一额外开销是一次堆分配。
Rc<T> 与 Arc<T>
引用计数智能指针,允许多个所有者共享同一数据。Rc 用于单线程,Arc 用于多线程(原子计数,开销略高)。
1 | |
1 | |
💡 提示:
Rc::clone只增加计数(O(1)),不深拷贝数据。Arc的 clone 使用原子操作,比Rc慢约 2-3 倍,但仍是 O(1)。
⚠️ 注意:
Rc<T>不是Send/Sync——跨线程传递会在编译期报错。多线程场景必须用Arc<T>。
⚠️ 注意:循环引用会导致引用计数永远无法归零,造成内存泄漏。需要父指针时用
Weak<T>断开循环:
1 | |
🔄 对比:C++ 用
weak_ptr打破shared_ptr的循环引用,Rust 用Weak<T>打破Rc的循环引用——机制相同。
Cell<T> 与 RefCell<T>(内部可变性)
内部可变性(Interior Mutability)是 Rust 的设计模式:在外部 &T(不可变引用)下修改内部数据。这是对编译时借用规则的有条件绕开。
💡 提示:为什么需要内部可变性?有些场景下,从调用者角度看数据"逻辑上不可变",但内部实现需修改(如缓存、引用计数、惰性初始化)。编译时借用规则无法区分"逻辑不可变"和"物理不可变",故需运行时机制。
Cell<T>:按值存取,适合Copy类型。通过get/set/replace操作,不提供引用借用。
1 | |
RefCell<T>:运行时借用检查。borrow()返回Ref<T>,borrow_mut()返回RefMut<T>。违反借用规则时 panic。
1 | |
⚠️ 注意:
RefCell的borrow_mut()与已有borrow()同时存在时会 panic,而非编译错误。这是"将错误从编译期推迟到运行期"的权衡——务必在逻辑上保证不同时持有可变与不可变借用。
💡 提示:
Rc<RefCell<T>>是单线程中"多所有者 + 可变访问"的经典组合。多线程对应物是Arc<Mutex<T>>(详见第 13 章)。
Cow 与 Pin(进阶)
🔬 进阶:以下内容在特定场景下有用,初学者可跳过。
Cow<B>(写时克隆):借用或拥有的抽象——数据无需修改时零开销借用,需修改时才克隆。
1 | |
🔄 对比:
Cow≈ C++ 的 copy-on-write 惯用法,但 Rust 在类型层面区分了借用与拥有两种状态。
Pin<P> 与 Unpin:确保值在内存中不被移动,主要用于 async 生成的自引用 Future。大多数类型自动实现 Unpin(可安全移动),只有自引用类型需要 Pin 约束。
1 | |
⚠️ 注意:日常编码几乎不需要手动使用
Pin——async运行时(Tokio 等)会自动处理。只有实现自引用类型时才需深入理解。
智能指针选择指南
- 优先编译时借用规则(
&/&mut)——零开销,编译期保证安全 - 需要堆分配或递归类型 →
Box<T> - 单线程多所有者 →
Rc<T>;多线程 →Arc<T> - 需要内部可变性:
Copy类型 →Cell<T>,非Copy→RefCell<T> - 多线程可变共享 →
Arc<Mutex<T>>或Arc<RwLock<T>> - 循环引用 →
Weak<T>打破
💡 提示:更详细的对比、与 C++ 智能指针的对照、混合使用指南,见 Rust 智能指针。
迭代器
Rust 的 for 循环本质是迭代器语法糖——适配器惰性求值,由消费者驱动计算。
基本概念:Iterator trait
任何实现 Iterator trait 的类型都可遍历,只需定义 Item 类型和 next() 方法:
1 | |
next() 有值返回 Some,迭代结束返回 None。
1 | |
三种创建方式
与所有权模型一一对应:
| 方法 | 产生元素 | 所有权语义 |
|---|---|---|
iter() | &T | 借用(集合仍可用) |
iter_mut() | &mut T | 可变借用(可修改元素) |
into_iter() | T | 获取所有权(消费集合) |
1 | |
💡 提示:
for x in &v等价于v.iter(),for x in &mut v等价于v.iter_mut(),for x in v等价于v.into_iter()——for循环会隐式调用IntoIterator。
惰性适配器
适配器返回新迭代器,本身不计算(惰性):
| 适配器 | 作用 | 示例 |
|---|---|---|
map | 转换每个元素(1 进 1 出) | .map(|x| x * 2) |
filter | 保留满足条件的元素 | .filter(|&x| x > 0) |
filter_map | 过滤并转换(闭包返回 Option) | .filter_map(|s| s.parse().ok()) |
flat_map | 映射后扁平化(1 进多出) | .flat_map(|s| s.split_whitespace()) |
enumerate | 添加索引 | .enumerate() -> (usize, T) |
zip | 按索引配对两个迭代器 | .zip(other) |
take / skip | 取 / 跳过前 n 个 | .take(5) |
take_while / skip_while | 按条件取 / 跳过连续元素 | .take_while(|&x| x < 5) |
chain | 连接两个迭代器 | .chain(other) |
rev | 反转(需 DoubleEndedIterator) | .rev() |
cloned / copied | &T -> T | .copied() |
step_by | 按步长取元素 | .step_by(2) |
scan | 带可变状态的转换 | .scan(init, |st, x| ...) |
cycle | 无限循环(需 Clone) | .cycle().take(n) |
💡 提示:
filter的闭包接收的是元素的引用(&Self::Item),故常用|&x|模式解构引用——这是|x| *x的语法糖,让闭包体内直接用值而非引用。
消费者
消费者急切地触发实际计算:
| 消费者 | 作用 | 返回 |
|---|---|---|
collect | 收集为集合(依赖 FromIterator) | Vec/HashMap/HashSet/String |
sum / product | 求和 / 求积 | 数值 |
count | 计数 | usize |
max / min | 最大 / 最小值 | Option<T> |
any / all | 任一 / 全部满足 | bool(短路求值) |
find | 第一个满足条件的元素 | Option<T> |
position | 第一个满足条件的索引 | Option<usize> |
fold | 累积计算(可改变类型) | B |
reduce | 同类型归约 | Option<T> |
1 | |
collect:收集为不同类型
collect 依赖 FromIterator,需显式标注目标类型:
1 | |
fold 与 reduce
累积计算的核心——fold 提供初始值、可改变累积类型;reduce 用首元素作初始值、类型不变:
1 | |
💡 提示:
fold对空迭代器安全(直接返回初始值);reduce空集合返回None。
filter_map:过滤并转换
一次遍历完成"过滤 + 转换",比 filter().map() 更简洁高效——闭包返回 Some(value) 保留并转换,None 丢弃:
1 | |
迭代器链与惰性求值
多个适配器组合,体现函数式数据流水线:
1 | |
💡 提示:迭代器链是零成本抽象:编译后等价于手写循环,常因消除边界检查而比手写循环更快。
⚠️ 注意:忘记消费迭代器是常见错误——写了
.map(...)却没有.collect()或for,代码编译通过但什么都不做。编译器会发出"unused must_use"警告,务必留意。
自定义迭代器
实现 Iterator trait 的 Item 和 next() 即可,自动获得 map/filter/sum 等所有方法。
1 | |
💡 提示:切片(非迭代器)还有
chunks(n)(不重叠分块,末块可能不足)与windows(n)(滑动窗口,相邻窗口重叠)方法,常用于分批处理与移动平均(详见《切片(Slice)》)。itertoolscrate 提供fold_while(条件累积提前终止)、multiunzip等更多扩展。
闭包
闭包是可捕获环境变量的匿名函数,是配合迭代器的函数式利器,语法 |param| body。
闭包基础
1 | |
闭包与迭代器搭配
链式数据处理的惯用写法——迭代器适配器接收闭包作为参数:
1 | |
捕获 trait:Fn / FnMut / FnOnce
闭包根据捕获方式自动实现以下 trait,决定可被调用的方式:
| Trait | 捕获方式 | 可调用次数 | 典型场景 |
|---|---|---|---|
Fn | 不可变借用 &T | 多次 | 只读访问环境 |
FnMut | 可变借用 &mut T | 多次 | 修改环境变量 |
FnOnce | 获取所有权 T | 一次 | 消费环境变量(如 move 闭包传入线程) |
1 | |
💡 提示:Rust 编译器按"最小权限"原则自动推断闭包 trait——只需
&T就不会&mut T,只需&mut T就不会 move,避免不必要的限制。
🔄 对比:Rust 闭包的
Fn/FnMut/FnOnce三级 trait 比 C++ 按值/引用捕获更精细——编译器自动选择最小权限,而非依赖程序员手动指定捕获列表。Go 闭包则始终按引用捕获,可能导致意外的数据竞争。
move 闭包
当闭包需跨越作用域(如传入 thread::spawn)时,环境变量必须 move 而非借用——用 move 关键字强制移动捕获:
1 | |
⚠️ 注意:跨线程传递闭包几乎都需要
move,否则借用无法跨越线程生命周期,编译器会报"lifetime may not live long enough"。move只影响捕获方式,闭包最终实现Fn/FnMut/FnOnce仍取决于它如何使用捕获变量。
延伸阅读:
- Rust 智能指针(
Box/Rc/Arc/RefCell/Cell的对比、与 C++ 智能指针的对照、混合使用与选择指南)。



















