Rust 圈有一句常被吐槽的话:Rc 的"共享所有权"是个伪概念。很多人初学 Rust 会懵——明明文档里写着 Rc 是"shared ownership",怎么会是伪概念?
今天把这事掰开说。结论先放前面:Rc 不是假的"共享",但它确实不是 Rust 所有权体系里的东西——它是靠运行时硬拗出来的一种"例外"。理解了这一点,你才算真正摸到 Rust 所有权的门。
先看 Rust 所有权的真身
Rust 的所有权模型,最核心的一条原则是:每个值有且只能有一个"所有者"(owner)。
- 一个
String,在任意时刻只有一个变量拥有它。 - 当 owner 离开作用域,值自动被
drop,内存自动释放。 - 想"借用",只能通过
&(不可变借用)和&mut(可变借用),且每次只能有一种——要么多个不可变借用,要么一个可变借用。
这套规则在编译期检查,不靠运行时。它带来两个巨大红利:没有运行时垃圾回收、没有数据竞争。这是 Rust 敢自称"安全 + 高性能"的底气。
换句话说,一套纯粹而严格的 Rust,里面对所有权只有"独占"和"借用",压根没有"共享所有权"这个位置。
Rc 是怎么"打破规则"的
Rc(Reference Counting)是一个智能指针,它的做法是:把指针复制几份,每份都"指向"同一个堆上的值,内部维护一个引用计数。clone 一次,计数 +1;drop 一次,计数 -1;计数归零,堆上的值才被释放。
所以从结果看,它确实实现了"多个持有者共享同一份数据"——这跟"单一所有权"看上去是对着干的。
但关键在于:这个"共享"是运行时算出来的,不是编译期验证的。计数器的增减、归零的判断,全都在程序运行时发生。这跟你写"let x = 5; let y = x;"那种编译期就定死的所有权,完全是两套东西。
所以严格讲,Rc 的"共享所有权",是在 Rust 所有权体系之外,扒开一个洞,塞进去的一种运行时机制。说它"伪概念",不是骂它,而是提醒你:它不是你想象中那种"编译期安全的所有权",而是一笔需要你自己负责的"运行时账"。
这笔账,代价不少
只能共享"不可变"数据。Rc 本身不能给你可变引用。想改数据,得套一层
RefCell,把所有权的检查从编译期推迟到运行时——一旦在运行时违反借用规则,直接 panic。安全从"编译器兜底"变成了"你写代码时自己小心"。循环引用 = 内存泄漏。这是新手最容易踩的大坑。两个 Rc 互相持有对方,引用计数永远到不了 0,数据永远不被释放。Rc不检测环,你得自己用
Weak打破循环。很多"泄漏"不是 bug,是没意识到 Rc 会造环。非线程安全。Rc 的计数不是原子的,跨线程加减会出问题。多线程共享,必须用
Arc(原子引用计数)——所以你看,Rust 连"共享"都要分线程内外给你两套。有运行时开销。每次 clone/drop 都要增减计数。在性能敏感的热路径上,这成本不能忽略。
那到底什么时候用 Rc?
Rc 不是洪水猛兽,它解决的是一个真实需求:树状、图状、或者多处需要"共同访问同一份不可变数据"的结构。典型场景就是图结构、AST(抽象语法树)、或者一个需要被多处引用的不可变配置。
但用它之前,先问自己三个问题:
- 真的需要"共享"吗?很多时候用
&借用就够了,不需要拥有。Rc 是当你没法确定生命周期、又必须"共同拥有"时才上场。 - 会不会造环?会的话,想好哪个方向用
Weak。 - 多线程吗?是就换
Arc,别硬扛。
我的判断
“Rc 共享所有权是伪概念"这句话,真正的意思不是"Rc 不能用”,而是**“别把 Rc 当成 Rust 所有权的一部分来理解”**。Rust 的所有权,骨子里是"单一、独占、编译期验证";Rc 是"运行时计数、模拟共享、安全要靠你自己"。它是工具,不是原则。认清了这种区别,你才不会在RefCell、Weak、Arc这里踩得头破血流。
一个 Rust 学习者的笔记。觉得有启发点个赞,评论区聊聊你被 Rc 坑过哪些地方。