Rust 智能指针之 `Rc<T>`:引用计数与多所有权共享实战(TRPL 第 15 章)
教程文档【免费下载链接】bookThe Rust Programming Language项目地址https://gitcode.com/gh_mirrors/bo/book点击查看免费下载导读RcTReference Counting引用计数是 Rust 标准库中用于实现多所有权的核心智能指针它允许同一个堆上值被程序的多个部分同时持有所有权并通过计数决定何时安全回收。本篇文章基于《The Rust Programming Language》开源仓库的 src/ch15-04-rc.md 章节以经典的 cons list 链表为例完整演示如何用RcT替换BoxT解决一个值被多处共享的编译期所有权冲突并配合仓库中的可运行代码与输出讲透引用计数增减、Rc::clone与深拷贝clone的本质区别以及RcT为何只能用于单线程场景。读完你不仅能正确使用RcT还能理解它与后续RefCellT组合使用的缘由。什么是多所有权为什么单个值需要多个拥有者在大多数情况下Rust 的所有权模型清晰明确你能准确说出某个值归哪个变量所有。但现实中存在一种场景——同一个值可能有多个拥有者。最典型的例子是图graph数据结构多条边edge可能指向同一个节点node从概念上讲这个节点同时被指向它的所有边共同拥有。只有当不再有任何边指向该节点即它没有任何拥有者时它才能被安全清理。RcT正是为这种需求设计的标准库类型。它维护着一个对这个值有多少个引用的计数器当引用数为0时说明值已不再被使用可以安全清理且不会产生悬垂引用当引用数大于 0 时值保持有效。一个形象的类比RcT像客厅里的一台电视机。第一个人进屋时打开电视其他人可以陆续进来一起看只有当最后一个人离开房间时电视才会被关掉。如果有人还在看电视就把电视关了剩下的观众必然抗议——这和引用计数不足时提前释放数据导致的未定义行为如出一辙。何时该用RcT书中给出了明确的使用判据当你要在堆上分配数据供程序多个部分只读共享并且无法在编译期确定哪个部分会最后用完该数据时就用RcT。反过来说如果你能提前知道谁最后用完数据直接让那一方成为数据的唯一拥有者编译期的常规所有权规则就能生效无需RcT。单线程限定RcT只能用于单线程场景。它内部使用非原子的计数器在多线程下会产生数据竞争。多线程环境下的引用计数需要原子类型ArcT在第 16 章并发部分讲解仓库的 src/ch16-00-concurrency.md 一章对此有专门展开。共享数据实战两个链表共享第三个链表的所有权让我们回到书中第 15 章的 cons list 例子。回忆 Listing 15-5它用BoxT定义了递归的链表类型enum List { Cons(i32, BoxList), Nil, } use crate::List::{Cons, Nil}; fn main() { let list Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil)))))); }现在我们要创建两个链表b和c让它们共同拥有第三个链表a的所有权。结构如下图所示图 15-3链表b以元素 3 开头链表c以元素 4 开头而两者都指向链表a的首元素5 → 10 → Nil。用BoxT实现为什么失败E0382如果我们沿用BoxList的定义代码会写成 Listing 15-17 这样enum List { Cons(i32, BoxList), Nil, } use crate::List::{Cons, Nil}; fn main() { let a Cons(5, Box::new(Cons(10, Box::new(Nil)))); let b Cons(3, Box::new(a)); let c Cons(4, Box::new(a)); }编译时立即报错仓库中对应的错误输出 listing-15-17/output.txt 显示$ cargo run Compiling cons-list v0.1.0 (file:///projects/cons-list) error[E0382]: use of moved value: a -- src/main.rs:11:30 | 9 | let a Cons(5, Box::new(Cons(10, Box::new(Nil)))); | - move occurs because a has type List, which does not implement the Copy trait 10 | let b Cons(3, Box::new(a)); | - value moved here 11 | let c Cons(4, Box::new(a)); | ^ value used here after move原因很清楚Cons变体拥有它所持有的数据。创建b时a被移动进了bb成为a的唯一拥有者再想用a创建c时a已经被移走编译器直接拒绝。这也是所有权模型最典型的价值——在编译期就拦住数据被双重拥有导致的隐患。改用引用 生命周期行不行另一种思路是把Cons改成持有引用但这会引入生命周期参数且意味着链表中的每个元素都要活得和整个链表一样长。这在 Listing 15-17 的场景里恰好成立但在很多真实场景中并不满足例如节点在运行时才动态创建、随时可能被独立释放。因此更通用的方案是换用RcT。用RcT重写Rc::clone让引用计数从 1 涨到 3将List定义中的BoxT替换为RcT即可解决共享问题见 Listing 15-18enum List { Cons(i32, RcList), Nil, } use crate::List::{Cons, Nil}; use std::rc::Rc; fn main() { let a Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil))))); let b Cons(3, Rc::clone(a)); let c Cons(4, Rc::clone(a)); }关键变化有三点引入RcTRcT不在标准库 prelude 中必须显式use std::rc::Rc;引入用Rc::new构造a的数据被放进堆上的RcList中a本身是一个RcList用Rc::clone(a)共享创建b、c时不再把a移动走而是克隆a持有的RcList。每次调用Rc::clone指向同一份数据的引用计数就加 1创建a后计数为 1创建b后为 2创建c后为 3。只有当计数降到 0 时RcList里的数据才会被清理。从该示例的 Cargo.toml 可以看到这是一个名为cons-list、edition 2024的标准 Cargo 二进制项目没有任何第三方依赖你可以直接cargo run验证。为什么是Rc::clone(a)而不是a.clone()你完全可以写a.clone()但 Rust 社区的约定是在引用计数场景显式调用Rc::clone。原因很实际大多数类型的clone实现是深拷贝复制全部数据开销大Rc::clone的实现只递增引用计数不做任何数据深拷贝开销极小混用两种clone会让人分不清哪一次是昂贵的深拷贝、哪一次只是计数1。统一写Rc::clone在做性能排查时就能一眼跳过这些廉价调用只聚焦真正的深拷贝点。观察引用计数变化Rc::strong_count光说不练不行Listing 15-19 在main中增加了一个内部作用域包裹链表c并在每个计数变化点调用Rc::strong_count(a)打印当前强引用计数enum List { Cons(i32, RcList), Nil, } use crate::List::{Cons, Nil}; use std::rc::Rc; fn main() { let a Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil))))); println!(count after creating a {}, Rc::strong_count(a)); let b Cons(3, Rc::clone(a)); println!(count after creating b {}, Rc::strong_count(a)); { let c Cons(4, Rc::clone(a)); println!(count after creating c {}, Rc::strong_count(a)); } println!(count after c goes out of scope {}, Rc::strong_count(a)); }仓库中对应的实际运行输出 listing-15-19/output.txt 为$ cargo run Compiling cons-list v0.1.0 (file:///projects/cons-list) Finished dev profile [unoptimized debuginfo] target(s) in 0.45s Running target/debug/cons-list count after creating a 1 count after creating b 2 count after creating c 3 count after c goes out of scope 2输出验证了完整的行为闭环a创建后计数为1每次Rc::clone计数12、3c离开内部作用域后计数-1回到 2。计数下降是自动的Drop的实现注意下降不需要你调用任何函数。RcT实现了Droptrait当一个RcT值离开作用域时Drop会自动把引用计数减 1。这个例子看不到的隐藏部分是当b、a在main末尾依次离开作用域后计数归零RcList中的数据被完全清理。RcT的核心价值由此体现一个值可以有多个拥有者且只要还有任何一个拥有者存活计数就大于 0值就保证有效。为什么方法叫strong_count而不是countRcT除了强引用计数还有weak_count弱引用计数。weak_count的用途是防止引用循环reference cycles如果两个RcT互相持有对方强引用计数将永远无法归零造成内存泄漏。书中下一节 Preventing Reference Cycles UsingWeakT专门讲解了如何用WeakT打破这种循环。为什么RcT只允许不可变共享RcT通过不可变引用让你在程序的多个部分之间共享只读数据。它刻意不提供多路可变引用因为那会违反第 4 章讨论的借用规则之一对同一位置的多个可变借用会引发数据竞争与状态不一致。但现实需求往往需要多处共享 可变。解决方案正是本书接下来要讲的内部可变性interior mutability模式把RcT与RefCellT组合使用——RcT负责共享所有权RefCellT在运行时检查借用规则、允许安全地修改共享数据。这部分内容在仓库的 src/ch15-05-interior-mutability.md 中有完整实现与示例。小结与使用建议适用场景堆上数据需要被程序的多个部分只读共享且无法在编译期确定谁最后用完——用RcT两个 API 记牢Rc::new创建、Rc::clone递增计数廉价非深拷贝、Rc::strong_count观察计数自动回收计数归零由Drop自动完成无需手动减计数线程边界单线程用RcT多线程请改用原子计数版本的ArcT见 src/ch16-00-concurrency.md可变需求RcT只支持不可变共享若要修改共享数据需配合RefCellT使用内部可变性见 src/ch15-05-interior-mutability.md循环风险RcT强引用可能形成循环导致泄漏必要时用WeakT打断循环见 src/ch15-06-reference-cycles.md。想要亲手验证可进入仓库对应示例目录如listings/ch15-smart-pointers/listing-15-18/、listing-15-19/执行cargo run直观观察编译错误与引用计数的实时变化。赞分享教程文档【免费下载链接】bookThe Rust Programming Language项目地址https://gitcode.com/gh_mirrors/bo/book点击查看免费下载相关推荐Rust 引用计数智能指针 RcT 完全指南多所有权共享与引用计数原理The Rust Programming Language 实战解析Rust 引用计数智能指针 RcT 完全指南多所有权共享与引用计数原理The Rust Programming Language 实战解析 RcT 教程文档Rust Arc 原子引用计数详解跨线程安全共享所有权与实战Rust Arc 原子引用计数详解跨线程安全共享所有权与实战 Rust 的所有权模型保证了内存安全但也使得多个持有者共享同一份数据成为一个需要专门工具解文档教程Rust 关联关系Association实战用 Rc、Weak 与 RefCell 实现共享所有权与双向对象关联Rust 关联关系Association实战用 Rc 、 Weak 与 RefCell 实现共享所有权与双向对象关联 导读 在 Rust 中实现 OOP示例工程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

Vulkan稀疏资源实战:内存分配、稀疏绑定与虚拟纹理流式加载

Vulkan稀疏资源实战:内存分配、稀疏绑定与虚拟纹理流式加载

如果你在项目里做过 8K、16K 分辨率贴图的流式加载,那你多半遇到过一个很现实的局面:整张纹理一次性往显存里塞,要么分配失败,要么可用显存直接被吃掉一大块。Vulkan 内存分配把显存管理权完全交到应用层,堆、类型、属…

2026/10/9 13:31:29 阅读更多 →
10 分钟给 Windows 11 去臃肿:Win11Debloat 新手上手指南

10 分钟给 Windows 11 去臃肿:Win11Debloat 新手上手指南

10 分钟给 Windows 11 去臃肿:Win11Debloat 新手上手指南 【免费下载链接】Win11Debloat A simple, lightweight PowerShell script that allows you to remove pre-installed apps, disable telemetry, as well as perform various other changes to declutter and…

2026/10/9 13:31:38 阅读更多 →
重构CPython:三大关键设计如何重塑Python的未来

重构CPython:三大关键设计如何重塑Python的未来

1. 先把“重构 CPython”这件事说清楚CPython 是 Python 的默认解释器,也是绝大多数人跑python x.py时真正执行的那套 C 代码。提到“重构 CPython”,很多人第一反应是“核心团队才需要关心的事”,但你写过 C 扩展、调过解释器参数、或者只是…

2026/10/9 13:31:35 阅读更多 →

最新新闻

Java面试翻车现场:HashMap、线程池、JVM深度拆解

Java面试翻车现场:HashMap、线程池、JVM深度拆解

“严肃面试官 vs 搞笑水货程序员谢飞机(本名王大瓜)——互联网大厂 Java 面试实录与技术拆解”,光看这个标题你可能觉得是个段子,但我在现场的感觉是:这简直就是一场喜剧外壳下的技术解剖课。谢飞机,简历上…

2026/10/11 3:58:54 阅读更多 →
RT-Thread—STM32—环境搭建

RT-Thread—STM32—环境搭建

RT-Thread——STM32——环境搭建 概述 本教程主要根据官方推荐的教程进行环境搭建,但是在打包方面按照自己的习惯进行了打包。 RT-Thread官网有特别详细的教程,这儿就不详细说明RT-Thread官网 软件准备 MDK528a (Keil5)CubeMx_v5-2-0STM32CubeMx的支持…

2026/10/11 3:58:54 阅读更多 →
智能工厂建设方案全解析:从ISA-95架构到MES/SCADA系统选型

智能工厂建设方案全解析:从ISA-95架构到MES/SCADA系统选型

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 3:58:54 阅读更多 →
JavaScript核心考点索引:从原型链到事件循环的面试体系

JavaScript核心考点索引:从原型链到事件循环的面试体系

做前端面试辅导这几年,我收到最多的问题不是“这道题答案是什么”,而是“面对这么多考点,到底哪些才值得深学”。JavaScript知识体系太庞杂了,从语言基础到浏览器原理,从手写代码到性能优化,随便拉一个列表…

2026/10/11 3:58:53 阅读更多 →
第1章,[Win32 章节]:编程环境与 MSDN

第1章,[Win32 章节]:编程环境与 MSDN

专栏导航 上一篇:第1章,[Win32 章节]:编程语言与框架选择 回到目录 下一篇:第1章 :第一个 Win32 程序,头文件 本专栏课件 关于本专栏课件的获取方法,请参考下述课节。 参考课节&#xff1a…

2026/10/11 3:58:53 阅读更多 →
开源吐槽大会:开发者从项目吐槽中学到的避坑与成长之道

开源吐槽大会:开发者从项目吐槽中学到的避坑与成长之道

1. 这个标题是怎么“火”起来的:开源吐槽大会的由来与定位如果你混迹开发者社区有一阵子,大概率见过这类帖子:“某某开源项目到底能不能用”“维护者又跑路了”“README吹得天花乱坠,一跑就崩”。这些帖子往往评论区最热闹&#x…

2026/10/11 3:57:53 阅读更多 →

日新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/11 0:00:27 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/11 0:00:27 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 5:23:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 10:38:42 阅读更多 →