编程语言研究书库

智能指针:Box、Rc、RefCell 与 Weak — 第 15 章拆解

智能指针:Box、Rc、RefCell 与 Weak

这一章讲什么: 引用之外,标准库提供的一族"指针型类型"。它们各有额外能力和代价: Box(堆分配 + 间接层)、Rc(多所有者,引用计数)、RefCell(运行时才检查借用规则, 可变外 immutable 的值)。读完你要能选对:什么时候 Box、什么时候 Rc、什么时候 Rc<RefCell<T>>;以及知道引用循环是 Rust 不挡的内存泄漏,Weak 是标准解法。

1. 顶层全景:三种指针,三份合同

类型所有权借用检查时机可变吗
Box<T>单一编译期可
Rc<T>多个(计数)编译期(只读)否
RefCell<T>单一运行期(违规即 panic)可(运行时验证)

图说:默认(引用/Box)是"编译期、单主、可读写";每个智能指针放宽其中一条,代价标在格子里。Rc<RefCell<T>> 组合 = 多主 + 可写,代价是运行时的借用检查1。

2. Box<T>:间接层本身

Box 无额外开销(除了数据在堆上),常用三种场景:编译期大小未知的类型、大数据的廉价所有权转移(只搬指针)、只关心 trait 实现的类型(trait 对象)2。

主线例子:递归类型。 enum List { Cons(i32, List), Nil } 编不过——E0072,"recursive type has infinite size"。原因:Rust 要按"最大变体"算枚举的大小,Cons 里直接嵌一个 List 让这个过程无限递归3。修法就是编译器提示的那行:Cons(i32, Box<List>)——间接层(indirection)。Box 是指针,大小固定(一个 usize),Cons 的大小变成"i32 + 指针",有限了;被指向的 List 住堆上4。

3. Deref:像引用一样被对待

实现 Deref trait 让自定义类型支持 * 解引用。 手写 MyBox 对照:impl<T> Deref for MyBox<T> { type Target = T; fn deref(&self) -> &T { &self.0 } }。关键点:你写 *y,Rust 实际执行 *(y.deref())——deref 返回引用而不是值本身,因为返回值会被 move 出 self;外层那个 * 再对这个引用做一次常规解引用5。

deref coercion(解引用强制转换):函数参数类型不匹配时,编译器自动连打 deref,把 &MyBox<String> 变成 &String 再变成 &str 直到匹配。 例:hello(&m) 不需要写成 hello(&(*m)[..])。规则三条:&T→&U(T: Deref)、&mut T→&mut U(DerefMut)、&mut T→&U(单向:可变可降为不可变,反之不行——降了不破坏借用规则,升无法保证)6。转换次数编译期定死,零运行时开销。

4. Drop:出作用域自动清理

实现 Drop trait 的 drop(&mut self),值离开作用域时编译器自动插入调用——变量按创建顺序的逆序 drop7。两个硬规矩:① 不允许手动调 c.drop()(否则作用域结束时又 drop 一次,double free);要提前清理用 std::mem::drop(c) 函数8。② 有 Drop 的类型不能实现 Copy(第 4 章那条规则的家)。

5. Rc<T>:一份数据,多个主人

引用计数:Rc 记下有多少个指向同一份数据的引用,归零才清理。 什么时候需要:图结构里多条边指向同一节点、编译期说不准谁最后用完。书的比喻很传神:客厅电视,最后离开的人关灯9。Rc 只用于单线程(第 16 章讲多线程的 Arc)。

主线例子回到 cons list:Box 版里 let b = Cons(3, Box::new(a)); let c = Cons(4, Box::new(a)); 第二行 E0382——a 已经 move 进 b。改 Rc<List> 后:Cons(i32, Rc<List>),b、c 各自 Rc::clone(&a)。惯例:计数式克隆写 Rc::clone 而非 .clone()——它的实现只把计数 +1,不拷数据;用不同写法把"昂贵的深拷贝"和"廉价的计数"在代码里区分开,查性能时只需盯前者10。

Rc::strong_count 可见计数变化:创建 a = 1,克隆出 b = 2,c = 3,c 出作用域回 2;归零时数据才真正 drop——由 Rc 自己的 Drop 实现自动完成11。Rc 只给不可变访问:若允许多个可变引用,就是第 4 章的 data race 回来了。

6. RefCell<T>:把借用检查挪到运行时

内部可变性(interior mutability):一个对外 immutable 的值,通过内部手段修改自己的内容。 RefCell 单主,但它的借用规则在运行时用计数检查:borrow() 返回 Ref<T>、borrow_mut() 返回 RefMut<T>,RefCell 数着当前活跃几个;违规则不是编译错而是 panic("already borrowed: BorrowMutError")12。

什么时候值得:编译器太保守、拒绝了实际上正确的程序。 静态分析不可能证明一切(书里点了停机问题),编译器选择"宁拒不错";你确信逻辑安全时,RefCell 把检查推迟到运行时,换编译期过不了的代码能写。代价:错误可能拖到生产才炸 + 一点计数开销。教科书用例是 mock 对象:Messenger trait 的 send(&self) 只收不可变引用,而 mock 需要在 send 里记录消息(E0596)——把 sent_messages: Vec<String> 换成 RefCell<Vec<String>>,send 里 self.sent_messages.borrow_mut().push(...),外部不变性、内部可写,测试成立13。

组合技 Rc<RefCell<T>>:多主 + 可变。 Rc<RefCell<i32>> 的 cons list:value 被 a、b、c 共享,*value.borrow_mut() += 10 之后三处都变成 15。运行时检查防住 data race,用一点速度换灵活。(多线程版是 Arc<Mutex<T>>,第 16 章)14。

7. 引用循环:Rust 不挡的泄漏

内存泄漏在 Rust 里是"内存安全"的——防泄漏不是 Rust 的保证。 RefCell<Rc<List>> 能改指向,就有了造环的工具:让 a 指向 b、b 指向 a 后,两边 strong_count 都是 2;main 结束各自 -1 还剩 1,永不归零,永不 drop——泄漏。打印这种结构会无限递归直到栈溢出15。

预防的第一道是纪律:嵌套了内部可变性和引用计数的结构里,别造环——编译器抓不到,靠测试和 code review。第二道是数据结构层面:所有权关系用 Rc,非所有权关系用 Weak16。

**Weak<T> 的机制:Rc::downgrade(&rc) 得到一个 Weak,它把 weak_count +1 而不动 strong_count;Weak 不表达所有权,不参与"是否 drop"的判定;用的时候 .upgrade() 拿回 Option<Rc<T>>(值还在是 Some,已被 drop 是 None)。**树结构的教科书设计:父→子用 Rc(拥有),子→父用 Weak(知道即可)。计数实测:branch 的 strong=1/weak=1(leaf 的弱引用),branch 出作用域 strong 归 0,Node 被 drop,weak 引用毫无阻滞;此时 leaf 再 upgrade 只得 None——不会悬垂,也不会泄漏17。

8. 作者的判断与证据

  • "Box/Rc/RefCell 的选择表"——机制事实,章末总结成表1。
  • "Rc::clone 的写法约定"——惯例主张,理由是把性能审查面缩小10。
  • "编译期检查优于运行期,RefCell 只用于编译器证不了而你能证的场景"——设计立场,给了"保守的静态分析"这条理由12。
  • "防泄漏不是保证"——诚实的边界声明,很少见也很值钱15。

判断(我们的,不是书里的): 本章其实是第 4 章所有权教学的"放宽菜单": 每条默认规则都标好了"放宽价码"——多主(Rc)换运行期计数与只读, 运行期检查(RefCell)换 panic 风险,环(Weak 缺席时)换泄漏。 学会查价码再点菜,是本章真正的技能。 如果错,会错在: 实际工程里 Rc<RefCell<T>> 的链一旦变长,运行期 panic (而非编译错)的定位成本被低估;书把 RefCell 讲得很顺,但"违规在运行时才炸" 对大型代码库是真金白银的风险,这也是社区对 interior mutability 使用克制的部分原因。

9. 边界与局限

  • 全部三种都只适用单线程;多线程对位(Arc、Mutex)在第 16 章,本章只给了名字。
  • 自己实现智能指针只指了 Rustonomicon,没有展开。
  • DerefMut 的三条 coercion 规则给了,但何时该自己实现 DerefMut 没说。

10. 可带走的

  1. Box 的三个用途;递归类型必须加间接层(Box/Rc/&)。
  2. *y 即 *(y.deref());deref 返回引用防 move。
  3. deref coercion:参数类型不匹配自动连打 deref;&mut→& 单向。
  4. Drop 自动调、逆序、禁手动;提前清理用 std::mem::drop(c)。
  5. Rc = 多主 + 引用计数;克隆写 Rc::clone 与深拷贝区分;只读。
  6. RefCell = 单主 + 运行时借用检查;borrow/borrow_mut;违规 panic。
  7. Rc<RefCell<T>> = 多主可变,运行期防 data race。
  8. 引用循环 → strong_count 永不归零 → 泄漏;Rust 不挡。
  9. 非所有权关系用 Rc::downgrade 建 Weak;用前 upgrade() 拿 Option。
  10. 树:父→子 Rc,子→父 Weak。

11. 原文地图

主题原书章原文位置
Box 三用途Chapter 15: Smart Pointerstext/22-ch15-chapter-15-smart-pointers.txt:53(搜「can’t be known at compile time」)
递归类型 E0072同上text/22-ch15-chapter-15-smart-pointers.txt:163(搜「infinite size」) · text/22-ch15-chapter-15-smart-pointers.txt:227(搜「indirection」)
Deref 与 *(y.deref())同上text/22-ch15-chapter-15-smart-pointers.txt:436(搜「impl<T> Deref」) · text/22-ch15-chapter-15-smart-pointers.txt:462(搜「y.deref」)
deref coercion 三规则同上text/22-ch15-chapter-15-smart-pointers.txt:479(搜「Deref Coercion」) · text/22-ch15-chapter-15-smart-pointers.txt:547(搜「three cases」)
Drop 与 mem::drop同上text/22-ch15-chapter-15-smart-pointers.txt:567(搜「Drop Trait」) · text/22-ch15-chapter-15-smart-pointers.txt:674(搜「explicit destructor」)
Rc 与引用计数同上text/22-ch15-chapter-15-smart-pointers.txt:732(搜「Reference-Counted」) · text/22-ch15-chapter-15-smart-pointers.txt:844(搜「Rc::clone」)
strong_count 变化同上text/22-ch15-chapter-15-smart-pointers.txt:893(搜「count after creating a」)
RefCell 运行期检查同上text/22-ch15-chapter-15-smart-pointers.txt:933(搜「Enforcing Borrowing Rules」) · text/22-ch15-chapter-15-smart-pointers.txt:1274(搜「BorrowMutError」)
mock 对象用例同上text/22-ch15-chapter-15-smart-pointers.txt:1187(搜「RefCell<Vec<String>>」)
Rc<RefCell<T>>同上text/22-ch15-chapter-15-smart-pointers.txt:1291(搜「in combination with Rc」)
引用循环同上text/22-ch15-chapter-15-smart-pointers.txt:1357(搜「Can Leak Memory」)
Weak 与树同上text/22-ch15-chapter-15-smart-pointers.txt:1504(搜「Weak<T>」) · text/22-ch15-chapter-15-smart-pointers.txt:1583(搜「should not own its parent」)

Footnotes

  1. 出处:「Chapter 15: Smart Pointers」第 965 段(text/22-ch15-chapter-15-smart-pointers.txt:965,搜「reasons to choose」)。 ↩ ↩2

  2. 出处:「Chapter 15: Smart Pointers」第 53 段(text/22-ch15-chapter-15-smart-pointers.txt:53,搜「can’t be known at compile time」)。 ↩

  3. 出处:「Chapter 15: Smart Pointers」第 163 段(text/22-ch15-chapter-15-smart-pointers.txt:163,搜「infinite size」)。 ↩

  4. 出处:「Chapter 15: Smart Pointers」第 232 段(text/22-ch15-chapter-15-smart-pointers.txt:232,搜「indirection」)与第 271 段(text/22-ch15-chapter-15-smart-pointers.txt:271,搜「broken the infinite」)。 ↩

  5. 出处:「Chapter 15: Smart Pointers」第 436 段(text/22-ch15-chapter-15-smart-pointers.txt:436,搜「impl<T> Deref」)与第 462 段(text/22-ch15-chapter-15-smart-pointers.txt:462,搜「y.deref」)。 ↩

  6. 出处:「Chapter 15: Smart Pointers」第 479 段(text/22-ch15-chapter-15-smart-pointers.txt:479,搜「Deref Coercion」)与第 547 段(text/22-ch15-chapter-15-smart-pointers.txt:547,搜「three cases」)。 ↩

  7. 出处:「Chapter 15: Smart Pointers」第 556 段(text/22-ch15-chapter-15-smart-pointers.txt:556,搜「reverse」)。 ↩

  8. 出处:「Chapter 15: Smart Pointers」第 674 段(text/22-ch15-chapter-15-smart-pointers.txt:674,搜「explicit destructor」)。 ↩

  9. 出处:「Chapter 15: Smart Pointers」第 732 段(text/22-ch15-chapter-15-smart-pointers.txt:732,搜「Reference-Counted」)。 ↩

  10. 出处:「Chapter 15: Smart Pointers」第 844 段(text/22-ch15-chapter-15-smart-pointers.txt:844,搜「Rc::clone」)。 ↩ ↩2

  11. 出处:「Chapter 15: Smart Pointers」第 893 段(text/22-ch15-chapter-15-smart-pointers.txt:893,搜「count after creating a」)。 ↩

  12. 出处:「Chapter 15: Smart Pointers」第 933 段(text/22-ch15-chapter-15-smart-pointers.txt:933,搜「Enforcing Borrowing Rules」)。 ↩ ↩2

  13. 出处:「Chapter 15: Smart Pointers」第 1187 段(text/22-ch15-chapter-15-smart-pointers.txt:1187,搜「RefCell<Vec<String>>」)。 ↩

  14. 出处:「Chapter 15: Smart Pointers」第 1291 段(text/22-ch15-chapter-15-smart-pointers.txt:1291,搜「in combination with Rc」)。 ↩

  15. 出处:「Chapter 15: Smart Pointers」第 1357 段(text/22-ch15-chapter-15-smart-pointers.txt:1357,搜「Can Leak Memory」)。 ↩ ↩2

  16. 出处:「Chapter 15: Smart Pointers」第 1487 段(text/22-ch15-chapter-15-smart-pointers.txt:1487,搜「not easily done」)。 ↩

  17. 出处:「Chapter 15: Smart Pointers」第 1504 段(text/22-ch15-chapter-15-smart-pointers.txt:1504,搜「Weak<T>」)与第 1583 段(text/22-ch15-chapter-15-smart-pointers.txt:1583,搜「should not own its parent」)。 ↩

On this page