rust学习十五.3、智能指针相干的Deref和Drop特质

打印 上一主题 下一主题

主题 884|帖子 884|积分 2652

 
一、前言

智能指针为什么称为智能指针? 大概由于它所包含的额外功能。
这些额外的功能使得编码或者运行时让指针看起来更有效、并体现某些“智”的特征,所以,我猜测这应该是rust发明人这么称呼此类对象为智能的原因。
 
据前面有关章节所述,我们知道智能指针多基于结构体(struct)扩展实现。
我们知道,struct大体上相当于OOP的Class Object(类对象)。struct可以有自身方法,也可以实现特质。
所以,智能指针的所谓”智“(假如称为”魔力“之类也可以),多数来源于这些特质。 假如为了便于理解,也可以把这些特质称为插件,插件越多,一个武器的功能就越强大。
比如多功能道具,可以装上很多头,实现七零八落的功能。
 
这些特质中,有两个需要重点先容:Deref,Drop
 
无论是Box还是其它智能指针,都面临2个问题:如何访问指针指向的数据,如何释放指针所指向的数据。
访问数据涉及到编码的方便性,释放指针涉及到内存安全问题。
编码方便性要求rust提供一些更友好的书写方式,以便工程师能够更容易编写相对容易阅读的代码。要知道rust本身的语法已经够丑陋了,不能让指针把语法变得更加丑陋。
二、概念准备

引用(Reference -- ref)
指针本身就是引用。 前面有关文章中提到 &var,这个&就是构建一个指针,用于引用var的值。
排除引用(DeReference -Defef)
顾名思义,就是排除对xxx的引用。
但这实际有两个歧义:
1.不间接引用,而是直接用
2.不再引用,也无法用
结合有关册本上下文和rust的意思,应该理解为:不采用引用的方式,而是直接使用xxx值
rust中使用 * 表现排除引用。
丢掉/清算(Drop/clean- Drop)
和引用行为有关的别的一个概念。当引用完成后,排除对这些资源的控制,就称为丢掉。
三、Deref --排除引用

册本中关于Deref的作用概括:方式实现 Deref trait 的智能指针可以被当作常规引用来对待,可以编写操作引用的代码并用于智能指针
3.1Deref的Box实现

#[lang = "deref"]#[doc(alias = "*")]#[doc(alias = "&*")]#[stable(feature = "rust1", since = "1.0.0")]#[rustc_diagnostic_item = "Deref"]pub trait Deref {    /// The resulting type after dereferencing.    #[stable(feature = "rust1", since = "1.0.0")]    #[rustc_diagnostic_item = "deref_target"]    #[lang = "deref_target"]    type Target: ?Sized;
    /// Dereferences the value.    #[must_use]    #[stable(feature = "rust1", since = "1.0.0")]    #[rustc_diagnostic_item = "deref_method"]    fn deref(&self) -> &Self::Target;}//------------------------------------------------------- #[stable(feature = "rust1", since = "1.0.0")]
  1. impl<T: ?Sized, A: Allocator> Deref for Box<T, A> {
  2.     type Target = T;
  3.     fn deref(&self) -> &T {
  4.         &**self
  5.     }
  6. }
复制代码
type Target: ?Sized;  -- >表现目标是一个不固定大小的类型
Box的deref的返回是 &**self。
特别需要注意的是:deref函数的实现是比较怪异。
我们要理解:&**self是如何变成 &T的

  • 首先&self是指向Box的一个引用
  • *self,排除引用,直接得到Box。*self实际是*(&self)。
  • *(*self)=*(Box),Box本身包含一个指针,所以得到T
  • &(**self)=&(*(*(&Self)))=&(*Box)=&(T)
终极deref让我们得到Box指针的实际数据的引用。
--
注意:由于rust的一个约定,阅读对象方法体需要特别注意
rust对象方法通常形如 fn  xxxx(&self){}。
这存在一个默认的对自身的引用,而不是自身。但是我们又在方法体中写self.而这个self其实是&self。
 
3.2编译器对Deref的支持

很多时间,某个对象(struct,enum等)实现某个特质的时间,我们通过对象实例去调用特质方法来使用特质。
当Rust的做法是通过编译器的努力,让工程师可以不直接调用deref就能够实验deref,从而编写更加简单的代码,并实现deref的重要目标:智能指针可以被当作常规引用来对待
很多语言的编译器都雷同的行为,越是新的语言,越是新版本的编译器,体现越明显。比方java对匿名函数的支持,对郎打表达式的支持。
那么做的原因,纵然为了减轻工程师的负担:便利地得到某种功能,而不需要使用复杂的操作
 
更具体一点就是:工程师可以不要再写*号,在多个场景中,rustc编译器会实验替工程师调用deref。
根据书上描述的规则如下:

  • 当 T: Deref 时从 &T 到 &U。
  • 当 T: DerefMut 时从 &mut T 到 &mut U。
  • 当 T: Deref 时从 &mut T 到 &U
需要我们理解的是第3条:根据借用规则,假如有一个可变引用,其必须是这些数据的唯一引用(否则程序将无法编译);将不可变引用转换为可变引用则需要初始的不可变引用是数据唯一的不可变引用,而借用规则无法包管这一点。
因此,Rust 无法假设将不可变引用转换为可变引用是可能的。
然而不能理解也不重要,总之这是一个规定.
 
示例:
  1. use std::ops::Deref;
  2. fn hello_ref_string(name: &str) {
  3.     println!("Hello, {name}!");
  4. }
  5. fn main() {
  6.     //1.0 Box的隐式转换  -- &Box转为 &String,不需要写复杂的 &(*Box)
  7.     let eng=String::from("英国佬");
  8.     let eng_box=Box::new(eng);
  9.     hello_ref_string(&eng_box);  // 隐式转换,把 &Box转为 &String。 通过这种引用可以省掉 * 符号
  10.     //2.0 如果愿意,也可以使用*符号显式转换。
  11.     let german=String::from("德国佬");
  12.     let german_box=Box::new(german);
  13.     hello_ref_string(&*german_box);  // 这相当于*解除引用后得到T,再&T得到&T
  14.     //3.0  也可以显示调用deref方法
  15.     let french=String::from("法国佬");
  16.     let french_box=Box::new(french);
  17.     hello_ref_string(french_box.deref());  // 显式调用Deref方法,得到&T
  18. }
复制代码
 rustc的隐式转换,就是为了制止我们写 frech_box.deref()。工程师可以直接写 &frech_box就到达隐式调用deref的效果。
四、Drop

如书所言,Drop大概是rust最重要的一个特质,它负责在离开作用域后,为对象自动释放相干资源(文件,网络,内存...)。
然而Drop的妙处有不少:

  • 可以为rust的任意类型实现Drop
  • rust通过编译器的方式,会为对象插入离开特定作用域的代码-- 即调用对象的Drop实现 
  • 假如有多个对象需要释放,那么rust编译器会自动决定需要释放的次序(通常是创建对象时间的逆序,但不明白是否绝对)
  • Drop 无法被克制,也无需克制,由于它的初衷就是为了自动清算
  • Drop 也不能表现调用,但可以使用std::mem::drop()函数来提前释放资源. 这个函数其实就是重要调用Drop特质
  • 所有权系统确保引用总是有效的,也会确保 drop 只会在值不再被使用时被调用一次
通过这个机制,rust制止了工程师插入手动释放资源代码问题:贫苦、可能会忘记、可能释放次序存在错误等等。
这些规则都挺好理解。
据本人所知,rust的自有智能指针(String,Vec,Box,Rc,Ref,RefMut)都实现了Drop,但为什么enum没有实现?
此问题,临时不考虑了。
现添一个例子(模仿书本),演示Drop的调用机制,和手动调用drop函数:
  1. #[derive(Debug)]
  2. struct Student {
  3.     name: String,
  4.     age: u32,
  5. }
  6. impl Drop for Student {
  7.     fn drop(&mut self) {
  8.         println!("释放{}({}岁)的资源",self.name,self.age);
  9.     }
  10. }
  11. fn test_drop() {
  12.     let mut mao = Student {
  13.         name: String::from("高温i"),
  14.         age: 20,
  15.     };
  16.     mao.age = 21;
  17. }
  18. fn main() {
  19.     test_drop();
  20.     let lu = Student {
  21.         name: String::from("卢俊义"),
  22.         age: 40,
  23.     };
  24.     println!("{:#?}", lu);
  25.     std::mem::drop(lu);  //手动释放。如果不释放,那么程序退出的时候也会被自动调用一次。
  26.                          //drop 函数主要作用就是调用Drop特质,所以这里手动释放也行。
  27.     println!("main 结束");
  28. }
复制代码
实验效果:

从实验效果可以验证几个结论:

  • drop是自动调用的
  • 离开任意作用域都可能触发drop,就看变量的作用范围。有的作用于某个方法、函数,有的作用于主函数
  • 手动调用drop(),也会自动Drop特质方法
 
末了,看看Box的Drop实现:
  1. #[stable(feature = "rust1", since = "1.0.0")]
  2. unsafe impl<#[may_dangle] T: ?Sized, A: Allocator> Drop for Box<T, A> {
  3.     #[inline]
  4.     fn drop(&mut self) {
  5.         // the T in the Box is dropped by the compiler before the destructor is run
  6.         let ptr = self.0;
  7.         unsafe {
  8.             let layout = Layout::for_value_raw(ptr.as_ptr());
  9.             if layout.size() != 0 {
  10.                 self.1.deallocate(From::from(ptr.cast()), layout);
  11.             }
  12.         }
  13.     }
  14. }
复制代码
 
Box的此中一个成员(内存分配器)可以实验资源释放--具体而言就是堆内存释放。
五、小结

当学习了Deref和Drop两个特质之后,对于智能指针的“智能”更有体会了。
以下是个人的一些开端体会:

  • rust通过实现Deref和Drop大大方便了对数据的引用和对资源的释放,换言之,以往在雷同c++那用的事故变得相对简单了
  • rust通过得当的性能捐躯到达相对的内存安全以及相对高的性能,某种程度上是可以接受的。
  • 假如在特定的应用中,对于特定功能的性能比较执着,那么也可以考虑继承采用硬件代码/c/c++之类的语言编写
  • 智能指针的存在,是否意味着,在面向具体业务的编码中,大部门类型应该采用智能指针?
 
小结:

  • 通过实现Deref,智能指针可以被当作常规引用来对待
  • 通过实现Drop,可以解决智能指针资源的释放问题(条件是代码写对了)
  • Deref的deref()可以手动调用,而Drop的drop()是不是手动调用的
 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

惊雷无声

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表