Rust常用特型之Drop特型
Rust常用特型之Drop特型.md在Rust标准库中,存在很多常用的工具类特型,它们能帮助我们写出更具有Rust风格的代码。
今天,我们主要学习Drop
特型。
(注:本文更多的是对《Programing Rust 2nd Edition》的自己翻译和理解,并不是原创)
一、什么是Drop
当一个值不再拥有owner时(在Rust中每个值都有一个owner,并且最多只有一个owner),我们说Rust释放/清理(Drop)了该值。释放一个值通常意味着也需要一并释放它占用的其它资源,例如堆存储。释放可以发生在多种场合:例如变量超出作用域,表达式语句的结尾,截断一个向量并移除末尾的值等。
接下来的内容中,清理和释放表达的是同一个含义,均为drop的意思。
通常情况下,Rust会自动为你清理值。例如如下代码:
struct Appellation {name: String,nicknames: Vec<String>
}
这里我们来复习一下Vec<T>
的有关知识。
一个Vec<T>
由三个值构成, 第一个值是指针,它指向在堆上为元素分配的缓冲区。 该缓冲区由Vec<T>
本身拥有。第二值是缓冲区的容量Cap
。第三个值是当前元素的个数length
。它是一个胖指针。当缓冲区的大小达到它的容量时,再增加元素会重新分配一个更大的缓冲区,并将原来的元素复制过去,同时更新向量的指针,容量和长度值,最后释放旧的缓冲区。
一个Appellation
对象即包含了堆上的字符串内容(对应的name字段),又包含了堆上的向量元素缓冲区(对应nicknames
字段)。当这个对象释放时,Rust会小心清理所有资源,并不需要你自己做任何处理。然而,如果你愿意,你也可以通过实现std::ops::Drop
特型来自定义你的类型的清理方式这里为什么有个你的类型呢?因为Rust不允许特型和类型都是外部的,必须有一个是本地的。此时Drop
特型已经是外来的(相对于你的代码),因此类型必须是本地定义的。
Drop特型的定义为:
trait Drop {fn drop(&mut self);
}
个人理解,未必正确
我们可以看到,该特型仅有一个drop
函数,注意它的参数类型是&mut
,因为我们要做相关清理工作,因此必须是可变的。如果参数是mut self
会怎么样?那么相当于值转移到本函数中了,在本函数处理完毕后该值的owner
就不存在了,此时又到了调用drop
的场景,从而形成无限循环,所以参数类型必定为&mut
。
二、Drop特型的实现
当一个值被清理时,如果它实现了Drop
特型,那么Rust会自动调用它的drop
方法。该调用发生在清理它的内部元素或者字段之前。这说明用户自定义的drop
函数有第一优先权。当然这种隐匿调用也是调用drop
函数的唯一方式,如果你手动调用它,那么Rust会标记为一个错误。
这里也印证了上面提到的drop
函数的参数类型&mut
,因为发生在清理它的内部元素之前,所以该值在此时必须保留,所以不能是mut self
。也正因为如此,这个值一定是初始化过的(应该是变量初始化过)。
上面Appellation
类型的一个示例Drop
实现代码为:
impl Drop for Appellation {fn drop(&mut self) {print!("Dropping {}", self.name);if !self.nicknames.is_empty() {print!(" (AKA {})", self.nicknames.join(", "));}println!("");}
}
假定实现为上述代码,那么我们可以接下来写一段测试代码:
{let mut a = Appellation {name: "Zeus".to_string(),nicknames: vec!["cloud collector".to_string(),"king of the gods".to_string()]};println!("before assignment");a = Appellation { name: "Hera".to_string(), nicknames: vec![]};println!("at end of block");
}
那么运行得到的结果是什么呢?我们一行一行来分析代码:
- 1-6行,定义了一个类型为 Appellation 的mut变量a ,它的值在定义时已经初始化了
- 第7行,打印开始重新赋值信息
before assignment
并换行。 - 第8行,将a重新赋值,此时a原来的值被抛弃了,没有
owner
了,因此符合清理的条件,Rust会自动对其进行清理,在该值上调用drop
函数 drop
函数首先打印值的name
,这里应该是Dropping Zeus
。注意这里是print!
,未换行。- 接下来,因为
nicknames
不为空,将它的元素使用,
连接起来,所以应该为(AKA cloud collector,king of the gods)
。注意这里是print!
,未换行,因此是接在Dropping Zeus
之后。 - 接下来
println!("");
目的是产生换行。 - drop函数调用完毕,接下来回到示例代码第9行,打印
at end of block
。 - 第10行,示例代码结束,变量a超过作用域,在此释放,也会调用其
drop
函数。 - 再次回到
drop
函数,打印对象名称,此时应该为Dropping Hera
。 - 因为第二个
Appellation
值的nicknames
字段为空向量,所以不再打印AKA
相关。 - 再次换行。
最终输出结果为:
before assignment
Dropping Zeus (AKA cloud collector, king of the gods)
at end of block
Dropping Hera
上面的代码中,类型为Appellation
的变量a前后有两个不同的值,因此触发了两次清理。第一次清理发生在重新赋值时,此时第一个值被抛弃,变成了无owner
,所以触发清理。第二次发生在代码块结束 ,此时a超出作用域,也触发清理。
可以看到,我们的清理并没有清除掉内部元素占用的资源,这是Rust会在接下来自动处理的,我们的工作主要是作一些额外的处理。
针对这个问题,书中已经给了明确答案。Rust自动清理内部元素,而内部元素也会自动清理自己。例如Vec类型也实现了Drop
特型,它会清理掉它的内部元素并释放它占用的堆上的缓冲区。字符串内部使用Vec<u8>
来保存它的文本,因此字符串并不需要自己实现Drop
特型(Vec<T>
实现了就可以),向量本身来处理这些字符的释放。相同的原则应用于Appellation
值,向量的Drop
实现会自动释放它的元素。对于 Appellation
值本身,它也有一个owner
,它可以是本地临时变量或者某些数据结构,这个变量对释放它负责。
注意:
当一个变量的值被移走时,该变量就是未初始化的,因此在超过作用域时并不会触发drop,没有值需要清理。切记,清理的是值不是变量。
下面的一段代码:
let p;{let q = Appellation { name: "Cardamine hirsuta".to_string(),nicknames: vec!["shotweed".to_string(),"bittercress".to_string()] };if complicated_condition() {p = q;}
}
println!("Sproing! What was that?");
根据complicated_condition
返回值的不同,p或者q其中的一个在代码结束时会拥有这个Appellation
值,另一个变量是未初始化。这也决定了他们是在最后的println!
之前还是之后drop
(这是因为q的作用域在println!
之前结束而p的作用域在这之后结束)。虽然在Rust中一个值可以从一个变量移到另一个变量,但是只会清理一次。
通常情况下,你不需要给自己定义的类型实现Drop
特型,除非它拥有了Rust所不能自动处理的资源。例如,在Unix
系统中,Rust标准为使用如下的内部结构来代表操作系统文件描述:
struct FileDesc {fd: c_int,
}
其中fd
字段代表的文件描述数字在程序结束的时候应该关掉。标准库因此为之实现了Drop
特型来关掉它。
impl Drop for FileDesc {fn drop(&mut self) {let _ = unsafe { libc::close(self.fd) };}
}
这里,libc::close
是C语言库的close
函数的Rust名字,Rust只能在unsafe
代码块中调用C语言的函数。
知识点:
如果一个类型实现了Drop
特型,那么它不能再实现Copy
特型。如果一个类型是Copy
类型,那么意味着简单的字节复制就够了,这样可能会导致两个变量会拥有同一块数据。但是如果两个变量都面临清理时,相同的数据就会清理两次,这是一个错误。就好像上面的FileDesc
例子,如果它实现了Copy
特型,那么另一个变量也会关闭相同的fd
数字,显然这是一个错误。
进一步思考,如果把Copy
换成Clone
呢?经过测试是没有问题的。
use std::ops::Drop;
// A unit struct without resources
#[derive(Debug, Clone)]
struct Unit;impl Drop for Unit {fn drop(&mut self) {println!("in drop");}
}fn main() {let a = Unit;let b = a.clone();println!("over:{:?}",b);
}
运行结果为:
over:Unit
in drop
in drop
有人说那如果把FileDesc
设计为实现Clone
特型不一样么?其实还真不一样,因为fd
字段的排它性,所以把它设计为Clone
是错误的。只有可以复制的资源才能设计为实现Clone
特型,这个问题其实是Clone
特型的设计问题了,而不是Drop
特型的问题。
有人说如果两个变量都包含对同一块数据的引用,那么是不是清理两次呢?显然不是,引用不拥有值,不会触发清理。
标准前置还包含了一个drip
函数用来清理一个值,但是它的定义相当魔幻:
fn drop<T>(_x: T) { }
从代码中可以看出,它接收一个值并且获得了该值的owner
。在函数结束时_x
超出了作用域而会被Rust正常的清理掉。这里只是提供了一个便利功能,并不是手动调用值的drop
函数。
相关文章:
Rust常用特型之Drop特型
Rust常用特型之Drop特型.md在Rust标准库中,存在很多常用的工具类特型,它们能帮助我们写出更具有Rust风格的代码。 今天,我们主要学习Drop特型。 (注:本文更多的是对《Programing Rust 2nd Edition》的自己翻译和理解&…...

嵌入式 Linux 学习
在学习嵌入式 Linux 之前,我们先来了解一下嵌入式 Linux 有哪些东西。 1. 嵌入式 Linux 的组成 嵌入式 Linux 系统,就相当于一套完整的 PC 软件系统。 无论你是 Linux 电脑还是 windows 电脑,它们在软件方面的组成都是类似的。 我们一开电…...
Makedown语法
这里写自定义目录标题 欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自定义列表如何创建一个…...
SQLite语句
1.重写SQLiteOpenHelper // 例. public class MySQLiteOpenHelper extends SQLiteOpenHelper {public MySQLiteOpenHelper(Nullable Context context, Nullable String name, Nullable SQLiteDatabase.CursorFactory factory, int version) {super(context, name, factory, ve…...

Spring揭秘:Aware接口应用场景及实现原理!
内容概要 Aware接口赋予了Bean更多自感知的能力,通过实现不同的Aware接口,Bean可以轻松地获取到Spring容器中的其他资源引用,像ApplicationContext、BeanFactory等。 这样不仅增强了Bean的功能,还提高了代码的可维护性和扩展性&…...

校园小情书微信小程序,社区小程序前后端开源,校园表白墙交友小程序
功能 表白墙卖舍友步数旅行步数排行榜情侣脸漫画脸个人主页私信站内消息今日话题评论点赞收藏 效果图...
从Pandas到Polars :数据的ETL和查询
对于我们日常的数据清理、预处理和分析方面的大多数任务,Pandas已经绰绰有余。但是当数据量变得非常大时,它的性能开始下降。 本文将介绍如何将日常的数据ETL和查询过滤的Pandas转换成polars。 图片 Polars的优势 Polars是一个用于Rust和Python的Data…...

Node.Js编码注意事项
Node.js 中不能使用 BOM 和 DOM 的 API,可以使用 console 和定时器 APINode.js 中的顶级对象为 global,也可以用 globalThis 访问顶级对象 浏览器端js的组成 Node.js中的JavaScript组成 相比较之下发现只有console与定时器是两个API所共有的ÿ…...

floodfill算法题目
前言 大家好,我是jiantaoyab,在下面的题目中慢慢体会floodFill算法,虽然是新的算法,但是用的思想和前面的文章几乎一样,代码格式也几乎一样,但不要去背代码 图像渲染 https://leetcode.cn/problems/flood…...

AI相关的实用工具分享
AI实用工具大赏:赋能科研与生活,探索AI的无限可能 前言 在数字化浪潮汹涌而至的今天,人工智能(AI)已经渗透到我们生活的方方面面,无论是工作还是生活,都在悄然发生改变。AI的崛起不仅为我们带…...

K8s — PVC|PV Terminating State
在本文中,我们将讨论PV和PVC一直Terminating的状态。 何时会Terminting? 在以下情况下,资源将处于Terminating状态。 在删除Bounded 状态的PVC之前,删除了对应的PV,PV在删除后是Terminting状态。删除PVC时,仍有引用…...

C语言 --- 指针(5)
目录 一.sizeof和strlen对比 1.sizeof 2.strlen 3.strlen 和sizeof的对比 二.数组和指针笔试题目详解 回顾:数组名的理解 1.一维数组 2.字符数组 代码1: 代码2: 代码3: 代码4: 代码5: 代码6&am…...

Android Studio Iguana | 2023.2.1版本
Android Gradle 插件和 Android Studio 兼容性 Android Studio 构建系统基于 Gradle,并且 Android Gradle 插件 (AGP) 添加了一些特定于构建 Android 应用程序的功能。下表列出了每个版本的 Android Studio 所需的 AGP 版本。 如果特定版本的 Android Studio 不支持…...

并查集(蓝桥杯 C++ 题目 代码 注解)
目录 介绍: 模板: 题目一(合根植物): 代码: 题目二(蓝桥幼儿园): 代码: 题目三(小猪存钱罐): 代码: …...

MapReduce内存参数自动推断
MapReduce内存参数自动推断。在Hadoop 2.0中,为MapReduce作业设置内存参数非常繁琐,涉及到两个参数:mapreduce.{map,reduce}.memory.mb和mapreduce.{map,reduce}.java.opts,一旦设置不合理,则会使得内存资源浪费严重&a…...
pyside6 pytq PyDracula QVideoWidget视频只有画面没有声音
解决方案: 先不使用框架,纯pyside6代码,如果添加视频有画面有声音,那可以排除是硬件问题,如果没有画面只有声音,可能是视频解码器无法解码,换个格式的视频文件如果只有使用PyDracula 出问题&am…...

Axure基础 各元件的作用及介绍
图像热区 增加按钮或者文本的点击区域,他是透明的,在预览时看不见。 动态面板 用来绘制一下带交互效果的元件,他是动态的,如轮播图,一个动态面板里可以有多个子面板,每一个子面板对应着不同的效果。 他…...

学习Java的第六天
目录 一、变量 1、变量的定义 2、变量的声明格式 3、变量的注意事项 4、变量的作用域 二、常量 三、命名规范 Java 语言支持如下运算符: 1、算术运算符 解析图: 示例: 2、赋值运算符 解析图: 示例: 3、关…...

基于Spring Boot+ Vue的房屋租赁系统
末尾获取源码作者介绍:大家好,我是墨韵,本人4年开发经验,专注定制项目开发 更多项目:CSDN主页YAML墨韵 学如逆水行舟,不进则退。学习如赶路,不能慢一步。 目录 一、项目简介 二、开发技术与环…...
多轨迹建模方法的介绍与实操-基于R语言
本文介绍了多轨迹建模方法(Group-Based Multivariate Trajectory Modeling),这是一种扩展了单指标组基轨迹建模的技术,用于分析多个疾病生物标志物或临床重要因素的联合轨迹,以更好地理解和追踪疾病进程、行为或健康状…...
【杂谈】-递归进化:人工智能的自我改进与监管挑战
递归进化:人工智能的自我改进与监管挑战 文章目录 递归进化:人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管?3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...
Linux链表操作全解析
Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表?1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...

Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误
HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误,它们的含义、原因和解决方法都有显著区别。以下是详细对比: 1. HTTP 406 (Not Acceptable) 含义: 客户端请求的内容类型与服务器支持的内容类型不匹…...

基于FPGA的PID算法学习———实现PID比例控制算法
基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容:参考网站: PID算法控制 PID即:Proportional(比例)、Integral(积分&…...

【WiFi帧结构】
文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成:MAC头部frame bodyFCS,其中MAC是固定格式的,frame body是可变长度。 MAC头部有frame control,duration,address1,address2,addre…...
五年级数学知识边界总结思考-下册
目录 一、背景二、过程1.观察物体小学五年级下册“观察物体”知识点详解:由来、作用与意义**一、知识点核心内容****二、知识点的由来:从生活实践到数学抽象****三、知识的作用:解决实际问题的工具****四、学习的意义:培养核心素养…...

智能仓储的未来:自动化、AI与数据分析如何重塑物流中心
当仓库学会“思考”,物流的终极形态正在诞生 想象这样的场景: 凌晨3点,某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径;AI视觉系统在0.1秒内扫描包裹信息;数字孪生平台正模拟次日峰值流量压力…...

Git 3天2K星标:Datawhale 的 Happy-LLM 项目介绍(附教程)
引言 在人工智能飞速发展的今天,大语言模型(Large Language Models, LLMs)已成为技术领域的焦点。从智能写作到代码生成,LLM 的应用场景不断扩展,深刻改变了我们的工作和生活方式。然而,理解这些模型的内部…...
解决:Android studio 编译后报错\app\src\main\cpp\CMakeLists.txt‘ to exist
现象: android studio报错: [CXX1409] D:\GitLab\xxxxx\app.cxx\Debug\3f3w4y1i\arm64-v8a\android_gradle_build.json : expected buildFiles file ‘D:\GitLab\xxxxx\app\src\main\cpp\CMakeLists.txt’ to exist 解决: 不要动CMakeLists.…...
在 Spring Boot 项目里,MYSQL中json类型字段使用
前言: 因为程序特殊需求导致,需要mysql数据库存储json类型数据,因此记录一下使用流程 1.java实体中新增字段 private List<User> users 2.增加mybatis-plus注解 TableField(typeHandler FastjsonTypeHandler.class) private Lis…...