智能指针、迭代器与闭包
📚 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 | |
🔄 对比:
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 | |
RefCell<T>:运行时借用检查。borrow()返回Ref<T>,borrow_mut()返回RefMut<T>。违反借用规则时 panic。
1 | |
⚠️ 注意:
RefCell的borrow_mut()与已有的borrow()同时存在时会 panic,而非编译错误。这是"将错误从编译期推迟到运行期"的权衡——务必在逻辑上保证不会同时持有可变与不可变借用。
💡 提示:
Rc<RefCell<T>>是单线程中"多所有者 + 可变访问"的经典组合。多线程对应物是Arc<Mutex<T>>(详见第 10 章)。
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 智能指针。
迭代器与闭包
迭代器(Iterator)
Rust 的 for 循环本质是迭代器语法糖。任何实现 Iterator trait 的类型都可遍历——只需定义 Item 类型和 next() 方法。
1 | |
💡 提示:
iter()产生&T,iter_mut()产生&mut T,into_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 | |
💡 提示:迭代器是惰性的——
map/filter不会执行任何计算,直到被消费者(如collect/sum/for等)驱动。这意味着迭代器链是零成本抽象:编译后等价于手写循环。
⚠️ 注意:忘记消费迭代器是常见错误——写了
.map(...)却没有.collect()或for,代码编译通过但什么都不做。编译器会发出"unused must_use"警告,务必留意。
自定义迭代器:实现 Iterator trait 的 Item 和 next() 即可。
1 | |
闭包(Closure)
匿名函数,可捕获环境变量,语法 |param| body。
1 | |
闭包与迭代器搭配——链式数据处理的惯用写法:
1 | |
闭包捕获 trait:闭包根据捕获方式自动实现以下 trait,决定可被调用的方式:
| Trait | 捕获方式 | 可调用次数 | 典型场景 |
|---|---|---|---|
Fn | 不可变借用 &T | 多次 | 只读访问环境 |
FnMut | 可变借用 &mut T | 多次 | 修改环境变量 |
FnOnce | 获取所有权 T | 一次 | 消费环境变量(如 move 闭包传入线程) |
1 | |
💡 提示: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 迭代器。
延伸阅读:

