【Rust自学】15.7. 循环引用导致内存泄漏
说句题外话,这篇文章真心很难,有看不懂可以在评论区问,我会尽快作答的。
喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以关注专栏。谢谢喵!(=・ω・=)
15.7.1. 内存泄漏
Rust极高的安全性使得内存泄漏很难出现,但并不是完全不可能。
例如使用Rc<T>
和RefCell<T>
就可能创造出循环引用,造成内存泄漏:每个指针的引用计数都不会减少到0,值也不会被清理。
看个例子:
use crate::List::{Cons, Nil};
use std::cell::RefCell;
use std::rc::Rc;#[derive(Debug)]
enum List {Cons(i32, RefCell<Rc<List>>),Nil,
}impl List {fn tail(&self) -> Option<&RefCell<Rc<List>>> {match self {Cons(_, item) => Some(item),Nil => None,}}
}fn main() {let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil))));println!("a initial rc count = {}", Rc::strong_count(&a));println!("a next item = {:?}", a.tail());let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a))));println!("a rc count after b creation = {}", Rc::strong_count(&a));println!("b initial rc count = {}", Rc::strong_count(&b));println!("b next item = {:?}", b.tail());if let Some(link) = a.tail() {*link.borrow_mut() = Rc::clone(&b);}println!("b rc count after changing a = {}", Rc::strong_count(&b));println!("a rc count after changing a = {}", Rc::strong_count(&a));
}
-
首先创建了一个链表
List
,使用RefCell<T>
包裹Rc<T>
使其内部值可被修改 -
通过
impl
块为List
写了一个叫tail
的方法,用于获取List
下Cons
变体附带的第二个元素,如果有就返回其值,用Some
封装,是Nil
就返回None
。 -
然后在
main
函数创建了a
、b
两个List
的实例,b
内部共享了a
的值。这种链表的代码看着就犯恶心,所以我把其结构图放在
这里: -
main
函数里还通过Rc::strong_count
获取了a
和b
的强引用数量,使用自定义的tail
方法获了Cons
附带的第二个元素,用println!
打印出来。 -
下面使用
if let
语句把a
的Cons
的第二个值绑在link
上,通过borrow_mut
方法获得其可变引用&Cons
,使用解引用符号*
把它转为Cons
,最后把b
的值通过Rc::clone
共享赋给了link
,也就改变了a
内部的结构,变为了:
PS:我觉得自己画的太烂了,所以这里就换成The Rust Programming Language里的图片了
输出:
a initial rc count = 1
a next item = Some(RefCell { value: Nil })
a rc count after b creation = 2
b initial rc count = 1
b next item = Some(RefCell { value: Cons(5, RefCell { value: Nil }) })
b rc count after changing a = 2
a rc count after changing a = 2
第1行到第5行:刚开始创建a
时,引用数量就为1,当b
被声明时,a
被共享了,所以此时a
的引用计数为2,b
为1。
第六行到第7行:if let
语句把a
的内部结构改变了,使a
的第二个元素指向b
,b
的引用数量加1变为2。此时a
指向了b
,b
又指向了a
,就会造成循环引用。
当a
和b
都走出了作用域,Rust删除了变量b
,这将b
的引用计数从 2 减少到 1。此时Rc<List>
在堆上的内存不会被删除,因为它的引用计数是1,而不是0。然后 Rust 删除a
,这会将a
的Rc<List>
实例的引用计数从 2 减少到 1,如图所示。这个实例的内存也不能被删除,因为另一个实例的内存 Rc<List>
实例仍然引用它。分配给列表的内存将永远保持未回收状态。
接下来我们看看循环引用的内容是什么,使用这条代码:
println!("a next item = {:?}", a.tail());
Rust 将尝试打印此循环,其中a
指向b
指向a
等等,直到溢出堆栈。最终的结果会是栈溢出错误。
15.7.2. 防止内存泄漏的方法
那有什么方法来防止内存泄漏吗?这只能依靠开发者,不能依靠Rust。
不然就只能重新组织数据结构,把引用拆分成持有和不持有所有权的两种情况,一些引用用来表达所有权,一些引用不表达所有权。循环引用的一部分具有所有权关系,另一部分不涉及所有权关系。这样写只有所有权的指向关系才会影响到值的清理。
15.7.3. 把Rc<T>
换成Weak<T>
以防止循环引用
我们知道Rc::clone
会生成数据的强引用,使Rc<T>
内部的引用计数加1,而Rc<T>
只有在strong_count
为0时才会被清理。
然而,Rc<T>
实例通过调用Rc::downgrade
方法创建值的弱引用(Weak Reference)。这个方法的返回类型是weak<T>
(也是智能指针),每次调用Rc::downgrade
会为weak_count
加1而不是strong_count
,所以弱引用并不影响Rc<T>
的清理。
15.7.4. Strong vs. Weak
强引用(Strong Reference)是关于如何分析Rc<T>
实例的所有权。弱引用(Weak Reference)并不表达上述意思,使用它不会创建循环引用:当强引用数量为0时,弱引用就会自动断开。
使用弱引用之前需要保证它指向的值仍然存在。在Weak<T>
实例上调用upgrade
方法,返回Option<Rc<T>>
,通过Option
枚举来完成值是否存在的验证。
看个例子:
use std::cell::RefCell;
use std::rc::Rc;#[derive(Debug)]
struct Node {value: i32,children: RefCell<Vec<Rc<Node>>>,
}fn main() {let leaf = Rc::new(Node {value: 3,children: RefCell::new(vec![]),});let branch = Rc::new(Node {value: 5,children: RefCell::new(vec![Rc::clone(&leaf)]),});
}
Node
结构体代表一个节点,有两个字段:
value
字段存储当前值,类型是i32
。children
字段存储子节点,类型是RefCell<Vec<Rc<Node>>>
,这里使用Rc<T>
是为了让所有子节点共享所有权。具体来说,我们希望一个Node
拥有它的子节点,并且我们希望与储存这个节点的变量共享该所有权,以便我们可以直接访问树中的每个Node
。为此,我们将Vec<T>
项定义为Rc<Node>
类型的值。
这个例子的需求是每个节点都能指向自己的父节点和子节点。
再看一下main
函数:
- 创建了
leaf
,是Node
实例,value
为3,children
的值是被RefCell
包裹的空Vector
。 - 创建了
branch
,是Node
实例,value
为5,children
的值指向了leaf
。
这意味着leaf
它里面的Node
节点有两个所有者。目前可以通过branch
的children
字段访问leaf
;而反过来如果想通过leaf
来访问branch
暂时还不行,所以这里还需要修改。
想要实现需求就得用双向引用,但是双向的引用会创建循环引用,所以这时候就得使用Weak<T>
,避免产生循环:
struct Node {value: i32,parent: RefCell<Weak<Node>>,children: RefCell<Vec<Rc<Node>>>,
}
添加了parent
字段表示父节点,使用弱引用Weak<T>
。这里不用Vec<>
是因为这是个树结构,父节点只可能有一个。
这么写得把Weak<T>
引入作用域,还得重构下文,修改完后的整体代码如下:
use std::cell::RefCell;
use std::rc::{Rc, Weak};#[derive(Debug)]
struct Node {value: i32,parent: RefCell<Weak<Node>>,children: RefCell<Vec<Rc<Node>>>,
}fn main() {let leaf = Rc::new(Node {value: 3,parent: RefCell::new(Weak::new()),children: RefCell::new(vec![]),});println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());let branch = Rc::new(Node {value: 5,parent: RefCell::new(Weak::new()),children: RefCell::new(vec![Rc::clone(&leaf)]),});*leaf.parent.borrow_mut() = Rc::downgrade(&branch);println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
}
在leaf
被创建后先打印了其parent
字段的内容(这时parent
字段还没有内容);在branch
被创建后打印了leaf
的parent
字段内容(这时其内容就是branch
)。
*leaf.parent.borrow_mut() = Rc::downgrade(&branch);
这句话把branch
的内容从Rc<Node>
变为Weak<Node>
,指向了leaf
的parent
字段:
leaf.parent
是表示leaf
父节点的字段,其类型是RefCell<Weak<Node>>
,所以可以使用borrow_mut
来获得其可变引用&mut RefMut<Weak<Node>>
- 使用解引用符号
*
把可变引用&mut RefMut<Weak<Node>>
变为RefMut<Weak<Node>>
- 通过
downgrade
方法把branch
的Rc<Node>
变为Weak<Node>
并赋给parent
输出:
leaf parent = None
leaf parent = Some(Node { value: 5, parent: RefCell { value: (Weak) },
children: RefCell { value: [Node { value: 3, parent: RefCell { value: (Weak) },
children: RefCell { value: [] } }] } })
- 第一次打印时其
parent
字段还没有被赋值,所以其值是Option
下的None
变体。 - 第二次打印时其父节点已被指定为
branch
,不是无限输出表明此代码没有创建循环引用。
最后我们通过修改main
函数——添加打印语句和修改作用域来看看强引用和弱引用的数量:
fn main() {let leaf = Rc::new(Node {value: 3,parent: RefCell::new(Weak::new()),children: RefCell::new(vec![]),});println!("leaf strong = {}, weak = {}",Rc::strong_count(&leaf),Rc::weak_count(&leaf),);{let branch = Rc::new(Node {value: 5,parent: RefCell::new(Weak::new()),children: RefCell::new(vec![Rc::clone(&leaf)]),});*leaf.parent.borrow_mut() = Rc::downgrade(&branch);println!("branch strong = {}, weak = {}",Rc::strong_count(&branch),Rc::weak_count(&branch),);println!("leaf strong = {}, weak = {}",Rc::strong_count(&leaf),Rc::weak_count(&leaf),);}println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());println!("leaf strong = {}, weak = {}",Rc::strong_count(&leaf),Rc::weak_count(&leaf),);
}
代码的逻辑是:
-
创建完
leaf
之后打印里面有多少强引用和弱引用 -
这部分完了之后加了
{}
,创建了新的作用域:- 把
branch
的声明和指定leaf
父节点的操作放到里面 - 打印
branch
和leaf
在此时强引用、弱引用的数量
- 把
-
走出作用域后:
- 打印
leaf
的parent
- 打印
leaf
的强引用、弱引用
- 打印
输出:
leaf strong = 1, weak = 0
branch strong = 1, weak = 1
leaf strong = 2, weak = 0
leaf parent = None
leaf strong = 1, weak = 0
- 第1行:创建了
leaf
,只有一个强引用 - 第2行:创建了
branch
,由于branch
使用强引用对leaf
进行了关联,其parent
字段使用了Weak::new()
创建,所以branch
有1个强引用,一个弱引用 - 第3行:
branch
使用了leaf
的强引用,其本身在声明时又是一个强引用,所以此时leaf
就有两个强引用 - 第4行:由于
branch
已经走出其作用域,所以leaf
的parent
字段此时就为None
- 第5行:
branch
已经走出其作用域导致它对leaf
的强引用失效,leaf
的强引用减1变为1
相关文章:

【Rust自学】15.7. 循环引用导致内存泄漏
说句题外话,这篇文章真心很难,有看不懂可以在评论区问,我会尽快作答的。 喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以关注专栏。谢谢喵!(・ω…...
C#AWS signatureV4对接Amazon接口
马上要放假了,需要抓紧时间测试对接一个三方接口,对方是使用Amazon服务的,国内不多见,能查的资(代)料(码),时间紧比较紧,也没有时间去啃Amazon的文档,主要我的英文水平也不行,于是粗…...

C语言操作符(下)
上一篇文章传送门:操作符上 前言:上期我们介绍了C语言的操作符的使用方法,这期我们主要侧重讲当我们已经了解了操作符的基本知识后怎样样来看待运算路径的问题。 操作符 一,优先级和结合性1,优先级2,结合性…...
学习资料收藏 游戏开发
本文整理了本人在学习 Unity3D 游戏开发过程中知晓的一些学习资料。 视频教程 siki学院 M_Studio Unity中文课堂 博客 林新发 浅墨_毛星云 冯乐乐 Roystan Sorumi 宣雨松 陆泽西 书籍 《Unity 游戏设计与实现》(加藤政树) 《Unity Shader 入…...
我的2024年总结
趁着摸鱼赶紧写一下吧 去年目标review 还是将去年的目标完成了一些 【接纳不完美,多拍照片】 这个还是部分做到了,今年和一些朋友们见面时都注意拍照留记录了,不过还可以继续加强,因为外貌上发生了重大变化,下面细说…...
freeswitch在centos上编译过程
操作系统:centos9-last usr/local/freeswitch/bin/freeswitch -version FreeSWITCH version: 1.10.13-devgit~20250125T131725Z~3f1e4bf90a~64bit (git 3f1e4bf 2025-01-25 13:17:25Z 64bit)vi /etc/ssh/sshd_config ip a nmtui reboot ip a curl -o /etc/pki/rpm-…...
docker如何查看容器启动命令(已运行的容器)
docker ps 查看正在运行的容器 该命令主要是为了详细展示查看运行时的command参数 # 通过docker --no-trunc参数来详细展示容器运行命令 docker ps -a --no-trunc | grep <container_name>通过docker inspect命令 使用docker inspect,但是docker inspect打…...

正则表达式以及Qt中的使用
目录 一、正则表达式 1、基本匹配: 2、元字符: 2.1 .运算符: 2.2 字符集: 2.3 重复次数: 2.4 量词{} 2.5 特征标群() 2.6 或运算符 2.7 \反斜线转码特殊字符 2.8 锚点 3、简写字符 4、零宽度断言 4.1 正…...

当高兴、尊重和优雅三位一体是什么情况吗?
英语单词 disgrace 表示“失脸,耻辱,不光彩,名誉扫地”一类的含义,可做名词或动词使用,含义基本一致,只是词性不同。 disgrace n.丢脸;耻辱;不光彩;令人感到羞耻的人(或…...
Vue 3 中的 TypeScript:接口、自定义类型与泛型
在 Vue 3 中,TypeScript 提供了强大的类型系统,帮助我们更好地管理代码的类型安全。通过使用 接口(Interface)、自定义类型(Type Aliases) 和 泛型(Generics),我们可以编…...

【Super Tilemap Editor使用详解】(十六):高级主题:深入理解 Super Tilemap Editor
在本节中,我们将深入探讨 Super Tilemap Editor 的工作原理,特别是图块地图(Tilemap)的渲染机制以及如何优化性能。这些知识将帮助你更好地理解工具的内部机制,并在开发中做出更明智的决策。 一、图块地图与图块渲染 图块地图是 Super Tilemap Editor 的核心组件之一。它由…...
如何运用python爬虫爬取知网相关内容信息?
爬取知网内容的详细过程 爬取知网内容需要考虑多个因素,包括网站的结构、反爬虫机制等。以下是一个详细的步骤和代码实现,帮助你使用Python爬取知网上的论文信息。 1. 数据准备 首先,需要准备一些基础数据,如知网的URL、请求头…...

2025年数学建模美赛 A题分析(2)楼梯使用频率数学模型
2025年数学建模美赛 A题分析(1)Testing Time: The Constant Wear On Stairs 2025年数学建模美赛 A题分析(2)楼梯磨损分析模型 2025年数学建模美赛 A题分析(3)楼梯使用方向偏好模型 2025年数学建模美赛 A题分…...

云原生:构建现代化应用的基石
一、什么是云原生? 云原生是一种构建和运行应用程序的方法,旨在充分利用云计算的分布式系统优势,例如弹性伸缩、微服务架构、容器化技术等。云原生应用程序从设计之初就考虑到了云环境的特点,能够更好地适应云平台的动态变化&…...

18.Word:数据库培训课程❗【34】
目录 题目 NO1.2.3.4 NO5设置文档内容的格式与样式 NO6 NO7 NO8.9 NO10.11标签邮件合并 题目 NO1.2.3.4 FnF12:打开"Word素材.docx”文件,将其另存为"Word.docx”在考生文件夹下之后到任务9的所有操作均基于此文件:"Word.docx”…...

批量创建ES索引
7.x from elasticsearch import Elasticsearch# 配置 Elasticsearch 连接 # 替换为你的 Elasticsearch 地址、端口、用户名和密码 es Elasticsearch([http://10.10.x.x:43885],basic_auth(admin, XN272G9THEAPYD5N5QORX3PB1TSQELLB) )# # 测试连接 # try: # # 尝试获取集…...

RoboVLM——通用机器人策略的VLA设计哲学:如何选择骨干网络、如何构建VLA架构、何时添加跨本体数据
前言 本博客内解读不少VLA模型了,包括π0等,且如此文的开头所说 前两天又重点看了下openvla,和cogact,发现 目前cogACT把openvla的动作预测换成了dit,在模型架构层面上,逼近了π0那为了进一步逼近&#…...

25美赛ABCDEF题详细建模过程+可视化图表+参考论文+写作模版+数据预处理
详情见该链接!!!!!! 25美国大学生数学建模如何准备!!!!!-CSDN博客文章浏览阅读791次,点赞13次,收藏7次。通过了解比赛基本…...

基于RIP的MGRE VPN综合实验
实验拓扑 实验需求 1、R5为ISP,只能进行IP地址配置,其所有地址均配为公有IP地址; 2、R1和R5间使用PPP的PAP认证,R5为主认证方; R2与R5之间使用ppp的CHAP认证,R5为主认证方; R3与R5之间使用HDLC封…...

如何获取小程序的code在uniapp开发中
如何获取小程序的code在uniapp开发中,也就是本地环境,微信开发者工具中获取code,这里的操作是页面一进入就获取code登录,没有登录页面的交互,所以写在了APP.vue中,也就是小程序一打开就获取用户的code APP.…...

eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)
说明: 想象一下,你正在用eNSP搭建一个虚拟的网络世界,里面有虚拟的路由器、交换机、电脑(PC)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...
OpenLayers 可视化之热力图
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 热力图(Heatmap)又叫热点图,是一种通过特殊高亮显示事物密度分布、变化趋势的数据可视化技术。采用颜色的深浅来显示…...

简易版抽奖活动的设计技术方案
1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...

Swift 协议扩展精进之路:解决 CoreData 托管实体子类的类型不匹配问题(下)
概述 在 Swift 开发语言中,各位秃头小码农们可以充分利用语法本身所带来的便利去劈荆斩棘。我们还可以恣意利用泛型、协议关联类型和协议扩展来进一步简化和优化我们复杂的代码需求。 不过,在涉及到多个子类派生于基类进行多态模拟的场景下,…...
工业自动化时代的精准装配革新:迁移科技3D视觉系统如何重塑机器人定位装配
AI3D视觉的工业赋能者 迁移科技成立于2017年,作为行业领先的3D工业相机及视觉系统供应商,累计完成数亿元融资。其核心技术覆盖硬件设计、算法优化及软件集成,通过稳定、易用、高回报的AI3D视觉系统,为汽车、新能源、金属制造等行…...
MySQL中【正则表达式】用法
MySQL 中正则表达式通过 REGEXP 或 RLIKE 操作符实现(两者等价),用于在 WHERE 子句中进行复杂的字符串模式匹配。以下是核心用法和示例: 一、基础语法 SELECT column_name FROM table_name WHERE column_name REGEXP pattern; …...
#Uniapp篇:chrome调试unapp适配
chrome调试设备----使用Android模拟机开发调试移动端页面 Chrome://inspect/#devices MuMu模拟器Edge浏览器:Android原生APP嵌入的H5页面元素定位 chrome://inspect/#devices uniapp单位适配 根路径下 postcss.config.js 需要装这些插件 “postcss”: “^8.5.…...
Git常用命令完全指南:从入门到精通
Git常用命令完全指南:从入门到精通 一、基础配置命令 1. 用户信息配置 # 设置全局用户名 git config --global user.name "你的名字"# 设置全局邮箱 git config --global user.email "你的邮箱example.com"# 查看所有配置 git config --list…...

R 语言科研绘图第 55 期 --- 网络图-聚类
在发表科研论文的过程中,科研绘图是必不可少的,一张好看的图形会是文章很大的加分项。 为了便于使用,本系列文章介绍的所有绘图都已收录到了 sciRplot 项目中,获取方式: R 语言科研绘图模板 --- sciRplothttps://mp.…...
MySQL 索引底层结构揭秘:B-Tree 与 B+Tree 的区别与应用
文章目录 一、背景知识:什么是 B-Tree 和 BTree? B-Tree(平衡多路查找树) BTree(B-Tree 的变种) 二、结构对比:一张图看懂 三、为什么 MySQL InnoDB 选择 BTree? 1. 范围查询更快 2…...