首页 > 其他分享 >rust学习十五.3、智能指针相关的Deref和Drop特质

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

时间:2024-12-31 17:44:32浏览次数:1  
标签:Box self Drop 引用 Deref rust 指针

 

一、前言

智能指针为什么称为智能指针? 大概因为它所包含的额外功能。

这些额外的功能使得编码或者运行时让指针看起来更有效、并体现某些“智”的特征,所以,我猜测这应该是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")]
impl<T: ?Sized, A: Allocator> Deref for Box<T, A> {
    type Target = T;

    fn deref(&self) -> &T {
        &**self
    }
}

type Target: ?Sized;  -- >表示目标是一个不固定大小的类型

Box的deref的返回是 &**self。

特别需要注意的是:deref函数的实现是比较怪异。

我们要理解:&**self是如何变成 &T的

  1. 首先&self是指向Box的一个引用
  2. *self,解除引用,直接得到Box。*self实际是*(&self)。
  3. *(*self)=*(Box),Box本身包含一个指针,所以得到T
  4. &(**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<Target=U> 时从 &T&U
  • T: DerefMut<Target=U> 时从 &mut T&mut U
  • T: Deref<Target=U> 时从 &mut T&U

需要我们理解的是第3条:根据借用规则,如果有一个可变引用,其必须是这些数据的唯一引用(否则程序将无法编译);将不可变引用转换为可变引用则需要初始的不可变引用是数据唯一的不可变引用,而借用规则无法保证这一点。

因此,Rust 无法假设将不可变引用转换为可变引用是可能的。

然而不能理解也不重要,总之这是一个规定.

 

示例:

 
use std::ops::Deref;
 
fn hello_ref_string(name: &str) {
    println!("Hello, {name}!");
}

fn main() {
    //1.0 Box的隐式转换  -- &Box转为 &String,不需要写复杂的 &(*Box)
    let eng=String::from("英国佬");
    let eng_box=Box::new(eng);
    hello_ref_string(&eng_box);  // 隐式转换,把 &Box转为 &String。 通过这种引用可以省掉 * 符号

    //2.0 如果愿意,也可以使用*符号显式转换。

    let german=String::from("德国佬");
    let german_box=Box::new(german);
    hello_ref_string(&*german_box);  // 这相当于*解除引用后得到T,再&T得到&T 

    //3.0  也可以显示调用deref方法

    let french=String::from("法国佬");
    let french_box=Box::new(french);
    hello_ref_string(french_box.deref());  // 显式调用Deref方法,得到&T
}

 rustc的隐式转换,就是为了避免我们写 frech_box.deref()。工程师可以直接写 &frech_box就达到隐式调用deref的效果。

四、Drop

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

然而Drop的妙处有不少:

  1. 可以为rust的任意类型实现Drop
  2. rust通过编译器的方式,会为对象插入离开特定作用域的代码-- 即调用对象的Drop实现 
  3. 如果有多个对象需要释放,那么rust编译器会自动决定需要释放的顺序(通常是创建对象时间的逆序,但不明确是否绝对)
  4. Drop 无法被禁止,也无需禁止,因为它的初衷就是为了自动清理
  5. Drop 也不能显示调用,但可以使用std::mem::drop()函数来提前释放资源. 这个函数其实就是主要调用Drop特质
  6. 所有权系统确保引用总是有效的,也会确保 drop 只会在值不再被使用时被调用一次

通过这个机制,rust避免了工程师插入手动释放资源代码问题:麻烦、可能会忘记、可能释放顺序存在错误等等。

这些规则都挺好理解。

据本人所知,rust的自有智能指针(String,Vec,Box,Rc,Ref,RefMut)都实现了Drop,但为什么enum没有实现?

此问题,暂时不考虑了。

现添一个例子(模仿书本),演示Drop的调用机制,和手动调用drop函数:

#[derive(Debug)]
struct Student {
    name: String,
    age: u32,
}
impl Drop for Student {
    fn drop(&mut self) {
        println!("释放{}({}岁)的资源",self.name,self.age);
    }
}
fn test_drop() {
    let mut mao = Student {
        name: String::from("高温i"),
        age: 20,
    };
    mao.age = 21;
}
fn main() {
    test_drop();
    let lu = Student {
        name: String::from("卢俊义"),
        age: 40,
    };
    println!("{:#?}", lu);
    std::mem::drop(lu);  //手动释放。如果不释放,那么程序退出的时候也会被自动调用一次。 
                         //drop 函数主要作用就是调用Drop特质,所以这里手动释放也行。
    println!("main 结束");
}

执行结果:

从执行结果可以验证几个结论:

  1. drop是自动调用的
  2. 离开任意作用域都可能触发drop,就看变量的作用范围。有的作用于某个方法、函数,有的作用于主函数
  3. 手动调用drop(),也会自动Drop特质方法

 

最后,看看Box的Drop实现:

#[stable(feature = "rust1", since = "1.0.0")]
unsafe impl<#[may_dangle] T: ?Sized, A: Allocator> Drop for Box<T, A> {
    #[inline]
    fn drop(&mut self) {
        // the T in the Box is dropped by the compiler before the destructor is run

        let ptr = self.0;

        unsafe {
            let layout = Layout::for_value_raw(ptr.as_ptr());
            if layout.size() != 0 {
                self.1.deallocate(From::from(ptr.cast()), layout);
            }
        }
    }
}

 

Box的其中一个成员(内存分配器)可以执行资源释放--具体而言就是堆内存释放。

五、小结

当学习了Deref和Drop两个特质之后,对于智能指针的“智能”更有体会了。

以下是个人的一些初步体会:

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

 

小结:

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

 

标签:Box,self,Drop,引用,Deref,rust,指针
From: https://www.cnblogs.com/lzfhope/p/18637254

相关文章

  • [rustGUI][iced]基于rust的GUI库iced(0.13)的部件学习(00):iced简单窗口的实现以及在窗口显
    前言本文是关于iced库的部件介绍,iced库是基于rust的GUI库,作者自述是受Elm启发。iced目前的版本是0.13.1,相较于此前的0.12版本,有较大改动。本合集是基于新版本的关于分部件(widget)的使用介绍,包括源代码介绍、实例使用等。环境配置系统:window10平台:visualstudiocode语言:rust......
  • 【Rust自学】8.6. HashMap Pt.2:更新HashMap
    8.6.0.本章内容第八章主要讲的是Rust中常见的集合。Rust中提供了很多集合类型的数据结构,这些集合可以包含很多值。但是第八章所讲的集合与数组和元组有所不同。第八章中的集合是存储在堆内存上而非栈内存上的,这也意味着这些集合的数据大小无需在编译时就确定,在运行时它们......
  • 【Rust自学】8.4. String类型 Pt.2:字节、标量值、字形簇以及字符串的各类操作
    8.4.0.本章内容第八章主要讲的是Rust中常见的集合。Rust中提供了很多集合类型的数据结构,这些集合可以包含很多值。但是第八章所讲的集合与数组和元组有所不同。第八章中的集合是存储在堆内存上而非栈内存上的,这也意味着这些集合的数据大小无需在编译时就确定,在运行时它们......
  • 【Rust自学】5.2. struct使用例(加打印调试信息)
    对不起我都写到第8章了才发现我忘记发这一篇了,现在补上,不过这可能导致专栏的文章顺序有一点问题,但也只能将就着了。喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以关注专栏。谢谢喵!(=・ω・=)5.2.1.例子需求创建一个函数,计算长方形的面积,长......
  • Rust中以下三个东西是什么?有什么用? #[automatically_derived] #[allow(clippy::ptr_ar
    Rust中以下三个东西是什么?有什么用?#[automatically_derived]#[allow(clippy::ptr_arg)]#[rustfmt::skip]gpttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt在Rust中,以下三个属性分别有不同的用途:1.#[automatically_derived]作用:这个属性表示该代码是由......
  • delete,drop,truncate的区别
    delete操作功能:delete是用于从表中删除数据行的操作。它是一种数据操作语言(DML)语句,允许你根据指定的条件删除表中的部分或全部数据。例如,如果你想从一个名为employees的表中删除所有年龄大于60岁的员工记录,可以使用以下SQL语句:DELETEFROMemployeesWHEREage>6......
  • 你根据这个写一个完整的能运行的结合logos和lalrpop的Rust示例程序,并且要求有AST部分
    gpt好的,下面是一个结合logos和lalrpop的完整Rust示例,展示了如何使用logos编写词法分析器(lexer),然后用lalrpop来解析语法,并生成AST(抽象语法树)。这个示例将包括以下几个部分:logos词法分析器:用来从输入文本中生成tokens。lalrpop语法分析器:用来解析这些tokens,生成......
  • CSS系列(42)-- Backdrop Filter详解
    前端技术探索系列:CSSBackdropFilter详解......
  • Rust和C/C++相关调用总结
    一.Windows下Rust与C/C++互相调用1.C/C++调用rust1.1动态库调用1.1.1以LoadLibrary方式显示调用add.rs#[no_mangle]//防止Rust修改函数名pubextern"C"fnhello_world(){println!("HellofromRust!");}#[no_mangle]pubextern"C"fnadd(a:i32,b:i3......
  • rust的几种闭包类型
    前提知识:rust里面有move,copy,clone。所有对象都有一个类型,具体所有权。比如#[derive(Debug)]structComplex{real:f64,imag:f64,}fnmain(){leta=Complex{real:1.,imag:2.};letb=a;println!("{:?}",a);println!("{:?}",b);}会......