网站 用php asp源码 比较好,新手做站必看 手把手教你做网站,深圳自适应网站建设,织梦旅游网站模板Rust常用特型之Drop特型.md在Rust标准库中#xff0c;存在很多常用的工具类特型#xff0c;它们能帮助我们写出更具有Rust风格的代码。 今天#xff0c;我们主要学习Drop特型。
#xff08;注#xff1a;本文更多的是对《Programing Rust 2nd Edition》的自己翻译和理解存在很多常用的工具类特型它们能帮助我们写出更具有Rust风格的代码。 今天我们主要学习Drop特型。
注本文更多的是对《Programing Rust 2nd Edition》的自己翻译和理解并不是原创
一、什么是Drop
当一个值不再拥有owner时(在Rust中每个值都有一个owner并且最多只有一个owner)我们说Rust释放/清理(Drop)了该值。释放一个值通常意味着也需要一并释放它占用的其它资源例如堆存储。释放可以发生在多种场合例如变量超出作用域表达式语句的结尾截断一个向量并移除末尾的值等。
接下来的内容中清理和释放表达的是同一个含义均为drop的意思。
通常情况下Rust会自动为你清理值。例如如下代码
struct Appellation {name: String,nicknames: VecString
}这里我们来复习一下VecT的有关知识。
一个VecT由三个值构成, 第一个值是指针它指向在堆上为元素分配的缓冲区。 该缓冲区由VecT本身拥有。第二值是缓冲区的容量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特型它会清理掉它的内部元素并释放它占用的堆上的缓冲区。字符串内部使用Vecu8来保存它的文本因此字符串并不需要自己实现Drop特型VecT实现了就可以向量本身来处理这些字符的释放。相同的原则应用于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 dropT(_x: T) { }从代码中可以看出它接收一个值并且获得了该值的owner。在函数结束时_x超出了作用域而会被Rust正常的清理掉。这里只是提供了一个便利功能并不是手动调用值的drop函数。