bigywater.tech
Rust · 31

Weak<T> 解决循环引用

引用计数有一个经典缺陷:循环引用会让计数永远不归零。Rust 没有垃圾回收器,所以这是真泄漏。这篇用带 Drop 的节点把泄漏「看见」,再用 Weak 把循环打断,并验证计数与 upgrade 的行为。

2 → 1 就停
有循环的节点,块结束后强引用数只从 2 降到 1,永远到不了 0
None
数据释放后 weak.upgrade() 的返回值
Rc 向下 Weak 向上
树结构避免循环的惯用做法

Rc 循环泄漏

实测 · 本机执行输出
  源码:
      1 | use std::cell::RefCell;
      2 | use std::rc::Rc;
      3 | struct Node { name: &'static str, next: RefCell<Option<Rc<Node>>> }
      4 | impl Drop for Node { fn drop(&mut self) { println!("  drop {}", self.name); } }
      5 | fn main() {
      6 |     println!("-- 没有循环:离开作用域时两个节点都被 drop");
      7 |     { let a = Rc::new(Node { name: "a1", next: RefCell::new(None) }); let b = Rc::new(Node { name: "b1", next: RefCell::new(Some(Rc::clone(&a))) }); let _ = &b; }
      8 |     println!("-- 有循环:a2 → b2 → a2");
      9 |     {
     10 |         let a = Rc::new(Node { name: "a2", next: RefCell::new(None) });
     11 |         let b = Rc::new(Node { name: "b2", next: RefCell::new(Some(Rc::clone(&a))) });
     12 |         *a.next.borrow_mut() = Some(Rc::clone(&b));
     13 |         println!("  a 的强引用数 = {},b 的强引用数 = {}", Rc::strong_count(&a), Rc::strong_count(&b));
     14 |     }
     15 |     println!("-- 块结束了,但没有看到 drop a2 / drop b2:它们泄漏了");
     16 | }
  编译:成功
  运行(退出码 0):
    -- 没有循环:离开作用域时两个节点都被 drop
      drop b1
      drop a1
    -- 有循环:a2 → b2 → a2
      a 的强引用数 = 2,b 的强引用数 = 2
    -- 块结束了,但没有看到 drop a2 / drop b2:它们泄漏了

Weak 的行为

实测 · 本机执行输出
  源码:
      1 | use std::rc::{Rc, Weak};
      2 | struct D(&'static str);
      3 | impl Drop for D { fn drop(&mut self) { println!("  drop {}", self.0); } }
      4 | fn main() {
      5 |     let strong = Rc::new(D("数据"));
      6 |     let weak: Weak<D> = Rc::downgrade(&strong);
      7 |     println!("strong_count = {},weak_count = {}", Rc::strong_count(&strong), Rc::weak_count(&strong));
      8 |     println!("upgrade() 时数据还在:{}", weak.upgrade().map(|d| d.0).unwrap_or("已释放"));
      9 |     println!("upgrade 之后(临时 Rc 已释放)strong_count = {}", Rc::strong_count(&strong));
     10 |     drop(strong);
     11 |     println!("drop(strong) 之后,upgrade() = {:?}", weak.upgrade().map(|d| d.0));
     12 | }
  编译:成功
  运行(退出码 0):
    strong_count = 1,weak_count = 1
    upgrade() 时数据还在:数据
    upgrade 之后(临时 Rc 已释放)strong_count = 1
      drop 数据
    drop(strong) 之后,upgrade() = None

用 Weak 打断循环

实测 · 本机执行输出
  源码:
      1 | use std::cell::RefCell;
      2 | use std::rc::{Rc, Weak};
      3 | struct Node { name: &'static str, parent: RefCell<Weak<Node>>, children: RefCell<Vec<Rc<Node>>> }
      4 | impl Drop for Node { fn drop(&mut self) { println!("  drop {}", self.name); } }
      5 | fn main() {
      6 |     let leaf = Rc::new(Node { name: "leaf", parent: RefCell::new(Weak::new()), children: RefCell::new(vec![]) });
      7 |     println!("leaf 的父节点(挂接前) = {:?}", leaf.parent.borrow().upgrade().map(|p| p.name));
      8 |     {
      9 |         let branch = Rc::new(Node { name: "branch", parent: RefCell::new(Weak::new()), children: RefCell::new(vec![Rc::clone(&leaf)]) });
     10 |         *leaf.parent.borrow_mut() = Rc::downgrade(&branch);
     11 |         println!("leaf 的父节点(挂接后) = {:?}", leaf.parent.borrow().upgrade().map(|p| p.name));
     12 |         println!("branch:strong = {},weak = {};leaf:strong = {},weak = {}", Rc::strong_count(&branch), Rc::weak_count(&branch), Rc::strong_count(&leaf), Rc::weak_count(&leaf));
     13 |     }
     14 |     println!("branch 离开作用域之后,leaf 的父节点 = {:?}(leaf 的 strong_count = {})", leaf.parent.borrow().upgrade().map(|p| p.name), Rc::strong_count(&leaf));
     15 | }
  编译:成功
  运行(退出码 0):
    leaf 的父节点(挂接前) = None
    leaf 的父节点(挂接后) = Some("branch")
    branch:strong = 1,weak = 1;leaf:strong = 2,weak = 0
      drop branch
    branch 离开作用域之后,leaf 的父节点 = None(leaf 的 strong_count = 1)
      drop leaf

RefCell 的作用见RefCell<T> 运行时借用检查;Rc 的基本行为见Rc<T>。

分步演示

Weak
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。泄漏通过 Drop 不打印来观察,没有测内存占用
    • 没有验证:Arc 与 Weak 在多线程里的 upgrade 竞态、图结构更多的拥有方案(slotmap、arena)、Weak::new 的 0 开销、泄漏检测工具
    京ICP备2026067502号-1京公网安备11010502063487号