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函数。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/736892.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

应届生求职面试注意事项

应届生求职面试注意事项 引言 对于即将毕业的应届生来说&#xff0c;进入职场是一个全新的挑战。在面对众多竞争者的情况下&#xff0c;成功通过面试是获得理想工作的关键一步。本文将介绍一些应届生求职面试的注意事项&#xff0c;帮助应届生更好地应对面试&#xff0c;提高…

算法——滑动窗口之找到字符串中所有的字母异位词,串联所有单词的子串

6.找到字符串中所有的字母异位词 题目:. - 力扣&#xff08;LeetCode&#xff09; 6.1如何快速判断两个字符串是否是异位词 假设现在有s1 aabca,s2 abaca,那么这两个就是异位词,容易想到的判断方法就是将两个字符串按照字典序排序,再依次比较,但是时间复杂度很高;我们看看…

乌干达公司注册优势 乌干达公司注册的条件 乌干达公司注册的流程

乌干达公司注册优势 1、乌干达拥有稳定的政 治环境和开 放的市场经济&#xff0c;为企业提供了良好的发展机遇。 2、乌干达有着优越的地理位置&#xff0c;位于东非内陆&#xff0c;毗邻肯尼亚、坦桑尼亚和刚果&#xff08;金&#xff09;&#xff0c;是通往东非和中非的理想…

精通MySQL:从下载到部署,一切尽在掌握

MySQL数据库下载及安装教程 MySQL是一款广泛应用于各类项目的开源关系型数据库管理系统。它基于Structured Query Language&#xff08;SQL&#xff09;进行数据操作&#xff0c;具有高性能、易使用、成本低等优点。在这篇文章中&#xff0c;我们将向你介绍如何在不同操作系统…

代码随想录算法训练营第五十天| 583. 两个字符串的删除操作 、72. 编辑距离

文章目录 1.两个字符串的删除操作2.编辑距离 1.两个字符串的删除操作 给定两个单词 word1 和 word2&#xff0c;返回使得 word1 和 word2 相同所需的最小步数。 每步可以删除任意一个字符串中的一个字符。 示例 1&#xff1a; 输入: word1 “sea”, word2 “eat” 输出: 2 …

分享软件项目实施方案模板

本项目在实施过程中将遵守做到以下几个方面&#xff1a; 与建设单位共同完成整个系统软件、网络等设计,负责系统的开发、测试、调试、人员培训、系统的试运行和交付&#xff0c;并保证系统质量。负责系统的维护、应用软件的升级和更新。提出对系统硬件设备的相关技术要求。在项…

vue3 动态路由及使用动态路由后刷新界面出现空白页或者404

最近编写vue3动态路由的功能遇到了一些问题&#xff0c;处理好了&#xff0c;总结出来&#xff0c;希望能帮助到你。正片开始 先写好本地缓存菜单的方法&#xff08;存储、删除、获取&#xff09; // utils/menu.jsconst getMenuList () > {return JSON.parse(localStorag…

MachineSink - 优化阅读笔记

注&#xff1a;该优化与全局子表达式消除刚好是相反的过程&#xff0c;具体该不该做这个优化得看代价模型算出来的结果(有采样文件指导算得会更准确) 该优化过程将指令移动到后继基本块中&#xff0c;以便它们不会在不需要其结果的路径上执行。 该优化过程并非旨在替代或完全…

【大厂AI课学习笔记NO.80】深度学习行业人才能力图谱

深度学习领域的就业岗位及所需关键技术、工具、能力分析 深度学习作为人工智能领域的一个重要分支&#xff0c;近年来得到了飞速的发展。随着技术的不断进步和应用场景的不断拓展&#xff0c;深度学习领域的就业岗位也日益增多。本文将从领军人才、产业研发人才、应用开发人才…

Linux——信号

目录 什么是信号 Linux下的信号 信号的记录 信号处理的常见方式 产生信号 使用组合键产生信号&#xff08;包含core dump&#xff09; 使用系统调用向进程发送信号 由软件条件产生信号 由硬件异常产生信号 阻塞信号 内核表示 sigset_t 信号集操作函数 sigpendin…

jvm八股

文章目录 运行时数据区域Java堆对象创建对象的内存布局对象的访问定位句柄直接指针 GC判断对象是否已死引用计数算法可达性分析算法 引用的类别垃圾收集算法分代收集理论标记清除算法标记复制算法标记整理算法 实现细节并发的可达性分析 垃圾收集器serial收集器ParNew收集器Par…

如何保证 redis 的高并发和高可用?redis 的主从复制原理能介绍一下么?redis 的哨兵原理能介绍一下么?

目录 一、面试官心理分析 二、面试题剖析 1.Redis 主从架构 2.Redis replication 的核心机制 3.Redis 主从复制的核心原理 4.主从复制的断点续传 5.无磁盘化复制 6.过期 key 处理 7.复制的完整流程 8.全量复制 9.增量复制 10.heartbeat 11.异步复制 12.Redis 如何…

【三十】springboot项目上高并发解决示例

互相交流入口地址 整体目录&#xff1a; 【一】springboot整合swagger 【二】springboot整合自定义swagger 【三】springboot整合token 【四】springboot整合mybatis-plus 【五】springboot整合mybatis-plus 【六】springboot整合redis 【七】springboot整合AOP实现日志操作 【…

工程经济学二

一、名词解释 可行性分析&#xff1a; 可行性研究是在投资项目拟建之前&#xff0c;通过对与项目有关的市场、资源、工程技术、经济和社会等方面的问题进行全面分析、论证和评价&#xff0c;从而确定项目是否可行或选择最佳实施方案的一项工作 厂址选择 厂址选择是指在一个大…

Java学习记录(十六):IO流(三)

缓冲流 缓冲流是一种高级流&#xff0c;可以理解为将基本流包装成了这种高级流&#xff0c;而这种高级流的特点就是将一个缓冲区放入到基本流当中&#xff0c;从而提高基本流的效率&#xff0c;但实际进行读写操作的还是基本流 下面为缓冲字节输出流和缓冲字节输入流的应用&am…

2024考研王道计算机408数据结构+操作系统+计算机组成原理+计算机网络

2024考研王道计算机408数据结构操作系统计算机组成原理计算机网络 链-接&#xff1a;https://pan.baidu.com/s/152XLyH64TlcLXwmU-zlAsQ?pwdr7zf 提取码&#xff1a;r7zf 信道利用率在408中经常考察到这里&#xff0c;我给大家总结一下这一类题目的做题方法以及技巧。首先&a…

全量知识系统问题及SmartChat给出的答复 之20 语义关联+词扇+叙词库 之1

Q67. 请问语义关联和词扇之间有什么关系&#xff1f; 语义关联和词扇是两种不同的概念&#xff0c;但它们之间有一定的关系。 语义关联指的是词语之间在语义上存在联系或关联&#xff0c;可以是同义关系、反义关系、上下位关系等。而词扇是指一个中心词周围所有与之相关的词语…

深度强化学习(二)

#! https://zhuanlan.zhihu.com/p/686235508 深度强化学习(二)(贝尔曼方程) 一.贝尔曼方程&#xff08;将 Q π Q_\pi Qπ​ 表示成 Q π Q_\pi Qπ​ &#xff09; Theorem :假设 R t R_t Rt​ 是 S t 、 A t 、 S t 1 S_t 、 A_t 、 S_{t1} St​、At​、St1​ 的函数。…

基于SpringBoot疫情打卡健康评测系统

基于SpringBoot疫情打卡健康评测系统~ 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBootMyBatis 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 学生端功能效果展示 试卷表 在线考试 打卡管理 居家管理 学生返校申请管理 管理…

救命!我终于会写一份简单的产品说明书了

写一份简单的说明书其实也不简单&#xff0c;需要有前期的调研准备&#xff0c;撰写时的条框梳理&#xff0c;收尾的清晰明了。网络上有很多撰写产品说明书的方法&#xff0c;却也有一些模糊的地方&#xff0c;下面就来看看LookLook同学是怎么完成一份简单的产品说明书的。 一、…