Rust-解引用

news2025/1/11 5:52:15

“解引用”(Deref)是“取引用”(Ref)的反操作。取引用,我们有&、&mut等操作符,对应的,解引用,我们有操作符,跟C语言是一样的。示例如下:在这里插入图片描述
比如说,我们有引用类型p:&i32;,那么可以用
符号执行解引用操作。上例中,v1的类型是i32,p的类型是&i32,*p的类型又返回i32。

自定义解引用

解引用操作可以被自定义。方法是,实现标准库中的std::ops::Deref或者std::ops::DerefMut这两个trait。

Deref的定义如下所示。DerefMut的唯一区别是返回的是&mut型引用都是类似的,因此不过多介绍了。

在这里插入图片描述
这个trait有一个关联类型Target,代表解引用之后的目标类型。比如,标准库中实现了String向str的解引用转换:

在这里插入图片描述
deref()方法返回的类型是&Target,而不是Target。

如果说有变量s的类型为string,*s的类型并不等于s.deref()的类型。

*s的类型实际上是Target,即str。&*s的类型才是&str。

s.deref()的类型为&Target,即&str。

在这里插入图片描述
比如我们可以这样理解这几个类型:

  • Box是“指针”,指向一个在堆上分配的对象;
  • Vec是“指针”,指向一组同类型的顺序排列的堆上分配的对象,且携带有当前缓存空间总大小和元素个数大小的元数据;
  • string是“指针”,指向的是一个堆上分配的字节数组,其中保存的内容是合法的utf8字符序列。且携带有当前缓存空间总大小和字符串实际长度的元数据。
    以上几个类型都对所指向的内容拥有所有权,管理着它们所指向的内存空间的分配和释放。
  • Rc和Arc也是某种形式的、携带了额外元数据的“指针”,它们提供的是一种“共享”的所有权,当所有的引用计数指针都销毁之后,它们所指向的内存空间才会被释放。

自动解引用

Rust提供的“自动解引用”机制,是在某些场景下“隐式地”“自动地”帮我们做了一些事情。什么是自动解引用呢?下面用一个示例来说明:

在这里插入图片描述
编译,成功。查文档我们可以知道,len()这个方法的签名是:

fn len(&self)->usize

它接受的receiver参数是&str,因此我们可以用UFCS语法调用:

println!("length:{}",str::len(&s));

但是,如果我们使用&&&&&&&&&&str类型来调用成员方法,也是可以的。原因就是,Rust编译器帮我们做了隐式的deref调用,当它找不到这个成员方法的时候,会自动尝试使用deref方法后再找该方法,一直循环下去。

编译器在&&&str类型里面找不到len方法;尝试将它deref,变成&&str类型后再寻找len方法,还是没找到;继续deref,变成&str,现在找到len方法了,于是就调用这个方法。

自动deref的规则是,如果类型T可以解引用为U,即T:Deref,则&T可以转为&U:

自动解引用的用处

用Rc这个“智能指针”举例。Rc实现了Deref:

在这里插入图片描述
它的Target类型是它的泛型参数T。这么设计有什么好处呢?我们看下面的用法:

在这里插入图片描述
我们创建了一个指向string类型的Rc指针,并调用了bytes()方法。这里是不是有点奇怪?

这里的机制是这样的:Rc类型本身并没有bytes()方法,所以编译器会尝试自动deref,试试s.deref().bytes()。

String类型其实也没有bytes()方法,但是string可以继续deref,于是再试试s.deref().deref().bytes()。

这次在str类型中找到了bytes()方法,于是编译通过。

我们实际上通过Rc类型的变量调用了str类型的方法,让这个智能指针透明。这就是自动Deref的意义。

实际上以下写法在编译器看起来是一样的:

在这里插入图片描述
这就是为什么String需要实现Deref trait,是为了让&string类型的变量可以在必要的时候自动转换为&str类型。所以string类型的变量可以直接调用str类型的方法。

比如:

let s =String::from("hello");
let len =s.bytes();

虽然s的类型是string,但它在调用bytes()方法的时候,编译器会自动查找并转换为s.deref().bytes()调用。

所以String类型的变量就可以直接调用str类型的方法了。

同理:Vec类型也实现了Deref trait,目标类型是[T],&Vec类型的变量就可以在必要的时候自动转换为&[T]数组切片类型;Rc类型也实现了Deref trait,目标类型是T,Rc类型的变量就可以直接调用T类型的方法。

&*两个操作符连写跟分开写是不同的含义。以下两种写法是不同的:
在这里插入图片描述
fn joint()是可以直接编译通过的,而fn separate()是不能编译通过的。

因为编译器很聪明,它看到&*这两个操作连在一起的时候,会直接把&*s表达式理解为s.deref(),这时候p只是s的一个借用而已。

而如果把这两个操作分开写,会先执行*s把内部的数据move出来,再对这个临时变量取引用,这时候s已经被移走了,生命周期已经结束。

同样的,let p=&{*s};这种写法也编译不过。

这个花括号的存在创建了一个临时的代码块,在这个临时代码块内部先执行解引用,同样是move语义。

从这里我们也可以看到,默认的“取引用”、“解引用”操作是互补抵消的关系,互为逆运算。但是,在Rust中,只允许自定义“解引用”,不允许自定义“取引用”。

如果类型有自定义“解引用”,那么对它执行“解引用”和“取引用”就不再是互补抵消的结果了。先&后以及先后&的结果是不同的。

有时候需要手动处理

如果智能指针中的方法与它内部成员的方法冲突了怎么办呢?编译器会优先调用当前最匹配的类型,而不会执行自动deref,在这种情况下,我们就只能手动deref来表达我们的需求了。

比如说,Rc类型和String类型都有clone方法,但是它们执行的任务不同。

Rc::clone()做的是把引用计数指针复制一份,把引用计数加1。String::clone()做的是把字符串深复制一份。示例如下:

在这里插入图片描述

智能指针

Rust语言提供了所有权、默认move语义、借用、生命周期、内部可变性等基础概念。但这些并不是Rust全部的内存管理方式,在这些概念的基础上,我们还能继续抽象、封装更多的内存管理方式,而且保证内存安全。

引用计数

到目前为止,我们接触到的示例中都是一块内存总是只有唯一的一个所有者。

当这个变量绑定自身消亡的时候,这块内存就会被释放。

引用计数智能指针给我们提供了另外一种选择:一块不可变内存可以有多个所有者,当所有的所有者消亡后,这块内存才会被释放。

Rust中提供的引用计数指针有std::rc::Rc类型和std::sync::Arc类型。Rc类型和Arc类型的主要区别是:Rc类型的引用计数是普通整数操作,只能用在单线程中;Arc类型的引用计数是原子操作,可以用在多线程中。

这一点是通过编译器静态检查保证的。

首先我们用示例展示Rc智能指针的用法:

在这里插入图片描述
编译运行,结果显示:

$./testvalue :4242address :0x13958abdf200x13958abdf20

这说明,owner1 owner2里面包含的数据不仅值是相同的,而且地址也是相同的。

这正是Rc的意义所在。

从示例中可以看到,Rc指针的创建是调用Rc::new静态函数,与Box类型一致(将来会允许使用box关键字创建)。如果要创建指向同样内存区域的多个Rc指针,需要显式调用clone函数。

请注意,Rc指针是没有实现Copy trait的。

如果使用直接赋值方式,会执行move语义,导致前一个指针失效,后一个指针开始起作用,而且引用计数值不变。

如果需要创造新的Rc指针,必须手工调用clone()函数,此时引用计数值才会加1。

当某个Rc指针失效,会导致引用计数值减1。当引用计数值减到0的时候,共享内存空间才会被释放。

这没有违反我们前面讲的“内存安全”原则,它内部包含的数据是“不可变的”,每个Rc指针对它指向的内部数据只有读功能,和共享引用&一致,因此,它是安全的。

区别在于,共享引用对数据完全没有所有权,不负责内存的释放,Rc指针会在引用计数值减到0的时候释放内存。

Rust里面的Rc类型类似于C++里面的shared_ptr类型,且强制不可为空。

从示例中我们还可以看到,使用Rc访问被包含的内部成员时,可以直接使用小数点语法来进行,与T &T Box类型的使用方法一样。

原因我们在前面已经讲过了,这是因为编译器帮我们做了自动解引用。我们查一下Rc的源码就可以知道:

在这里插入图片描述
可见,Rc类型重载了“解引用”运算符,而且恰好Target类型指定的是T。

这就意味着编译器可以将Rc类型在必要的时候自动转换为&T类型,于是它就可以访问T的成员变量,调用T的成员方法了。因此,它可以被归类为“智能指针”。

下面我们继续分析Rc类型的实现原理。它的源代码在src/liballoc/rc.rs中,Rc类型的定义如下所示:

在这里插入图片描述
其中RcBox是这样定义的:

在这里插入图片描述
其中Shared类型我们暂时可以不用管它,当它是一个普通指针就好。

同时,它实现了Clone和Drop这两个trait。在clone方法中,它没有对它内部的数据实行深复制,而是将强引用计数值加1,如下所示:

在这里插入图片描述
在drop方法中,也没有直接把内部数据释放掉,而是将强引用计数值减1,当强引用计数值减到0的时候,才会析构掉共享的那块数据。

当弱引用计数值也减为0的时候,才说明没有任何Rc/Weak指针指向这块内存,它占用的内存才会被彻底释放。

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

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

相关文章

[笔记]深度学习入门 基于Python的理论与实现(三)

代码仓库 gitee 3. 神经网络 神经网络的出现就是为了解决设定权重的工作,即机器自动从数据中学习,确定合适的、能符合预期的输入与输出的权重。 3.1 从感知机到神经网络 神经网络和感知机有很多共同点,这里主要介绍差异 3.1.1 神经网络例子…

数据库|数据库范式(待完成)

文章目录 数据库的范式数据库的基本操作什么是数据库的范式产生的背景(没有规范化的坏处/带来的问题)规范化表格设计的要求五大范式的作用——树立标准打个比方——桥的承载能力1NF(1范式)如何转换成合适的一范式 2NF(…

Flask 项目怎么配置并创建第一个小项目?附上完成第一个小案例截图

目录 1. 为什么要学习 flask? 2. flask 是什么? 3. flask 如何使用? 要安装 Flask,可以按照以下步骤进行: 4. 使用流程 4.1. 新建项目 4.1.1. 打开 pycharm,新建项目 4.1.2. 设置目录,并…

centos7 arm服务器编译安装PaddlePaddle

前言 随着国产服务器发展,部署项目需要用在国产服务器上,官方教程里面很多没有讲解到,安装过程中出现了各种各样的问题,以下是对官方教程的补充,有什么问题,欢迎指正! 一、环境准备 gcc: 8.2版…

Python 网络编程之TCP详细讲解

【一】传输层 【1】概念 传输层是OSI五层模型中的第四层,负责在网络中的两个端系统之间提供数据传输服务主要协议包括**TCP(传输控制协议)和UDP(用户数据报协议)** 【2】功能 **端到端通信:**传输层负责…

数学建模-预测人口数据

目录 中国09~18年人口数据 创建时间 绘制时间序列图 使用专家建模器 得到结果 预测结果 残差的白噪声检验 中国09~18年人口数据 创建时间 路径:数据-> 定义日期和时间 绘制时间序列图 使用专家建模器 看看spss最终判断是那个模型最佳的契合 得到结果 预…

什么是NTFS格式文件系统?Tuxera NTFS for Mac2024下载步骤

一般磁盘格式分为:FAT、FAT32、NTFS,这几种格式目前是我们最常遇到的文件系统格式,其中现在遇到最多的就是NTFS格式,为更好地了解这类文件系统格式,小编今天专门介绍一下什么是NTFS格式文件系统以及它的特点和局限性。…

网络安全ctf比赛/学习资源整理,【解题工具、比赛时间、解题思路、实战靶场、学习路线】推荐收藏!

对于想学习或者参加CTF比赛的朋友来说,CTF工具、练习靶场必不可少,今天给大家分享自己收藏的CTF资源,希望能对各位有所帮助。 CTF在线工具 首先给大家推荐我自己常用的3个CTF在线工具网站,内容齐全,收藏备用。 1、C…

Chapter 10 类的继承(上篇)

目的:了解三种继承方式,并清楚其中的差别 🎃🎃🎃🎃🎃🎃🎃🎃🎃🎃🎃🎃🎃🎃🎃…

【JVM】垃圾回收 GC

一、前言 垃圾回收(Garbage Collection,GC)是由 Java 虚拟机(JVM)垃圾回收器提供的一种对内存回收的一种机制,它一般会在内存空闲或者内存占用过高的时候对那些没有任何引用的对象不定时地进行回收。以避免…

imgaug库指南(26):从入门到精通的【图像增强】之旅(万字长文!)

引言 在深度学习和计算机视觉的世界里,数据是模型训练的基石,其质量与数量直接影响着模型的性能。然而,获取大量高质量的标注数据往往需要耗费大量的时间和资源。正因如此,数据增强技术应运而生,成为了解决这一问题的…

在uni-app中使用sku插件,实现商品详情页规格展示和交互。

商品详情 - SKU 模块 学会使用插件市场,下载并使用 SKU 组件,实现商品详情页规格展示和交互。 存货单位(SKU) SKU 概念 存货单位(Stock Keeping Unit),库存管理的最小可用单元,通…

电子签章服务器,如何解决无纸化最后一公里?

钉钉、飞书、企微、OA、ERP等主流企业办公系统,无法实现电子签章,往往审批后还要将合同文件打印出来再进行签章。实现无纸化办公的这最后一公里就成了难题。电子签章服务器的出现,提供了完美的解决方案。本文将从专业角度,探讨电子…

大模型压缩与优化的技术原理与创新方法

目录 前言1 模型压缩简介2 知识蒸馏3 模型剪枝3.1 结构化剪枝3.2 非结构化剪枝 4 模型量化4.1 浮点表示 vs 定点表示4.2 位数选择与性能影响4.3 量化技术 5 其他模型压缩方法5.1 Weight Sharing: 参数共享5.2 Low-rank Approximation: 低秩分解5.3 Architecture Search: 神经网…

C++ 设计模式之观察者模式

【声明】本题目来源于卡码网(题目页面 (kamacoder.com)) 【提示:如果不想看文字介绍,可以直接跳转到C编码部分】 【设计模式大纲】 前面的文章介绍了创建型模式和结构型模式,今天开始介绍行为型模式。 【简介】什么是…

Oracle AWR报告的生成和解读

Oracle AWR报告的生成和解读 一、AWR报告概念及原理 Oracle10g以后,Oracle提供了一个性能检测的工具:AWR(Automatic Workload Repository 自动工作负载库)这个工具可以自动采集Oracle运行中的负载信息,并生成与性能相…

评估文字识别准确性的方法与流程

随着信息技术的发展,文字识别技术在各个领域得到了广泛的应用。然而,在实际应用中,如何评估文字识别的准确性,一直是相关领域的一个难题。本文将介绍几种常用的文字识别准确性评估方法,以期为相关领域的研究提供参考。…

Excel 添加复选框或选项按钮(表单控件)

Excel 添加复选框或选项按钮(表单控件) 要添加复选框或选项按钮,需要使用功能区上的“开发工具”选项卡。 注意: 若要启用“开发工具”选项卡,请按照以下说明进行操作: 在 Excel 2010 和后续版本中,选择“…

主流视频压缩格式

主流的视频压缩格式有很多,它们各自适用于不同的应用场景,如在线流媒体、广播、存档等。 以下是一些广泛使用的视频压缩格式: H.264/AVC (Advanced Video Coding): 目前最为广泛使用的视频压缩标准之一,兼容性极佳,广泛…

【蓝桥杯日记】第二篇——递归问题的处理

目录 前言 递归 递归解决的问题 递归的三要素 递归的练习(由浅入深) 1.循环改为递归 2.斐波那契 3.汉诺塔问题 总结 前言 大家好呀!我是大雄!一个菜鸡!接下来的几个月和大家分享一下自己在备战蓝桥中遇到的…