当前位置: 首页 > news >正文

Rust-解引用

“解引用”(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指针指向这块内存,它占用的内存才会被彻底释放。

相关文章:

Rust-解引用

“解引用”(Deref)是“取引用”(Ref)的反操作。取引用,我们有&、&mut等操作符,对应的,解引用,我们有操作符,跟C语言是一样的。示例如下: 比如说,我们有引用类型p:&i32;,那么可以用符…...

记录一下vue项目引入百度地图

公共部分 #allmap { width: 500px; height: 500px; font-family: "微软雅黑"; } 1、 <div id"allmap"> <baidu-map :center"center" :zoom"zoom" ready"handler"></baidu-map> </div> data()…...

基于Docker官方php:7.4.33-fpm镜像构建支持67个常见模组的php7.4.33镜像

实践说明&#xff1a;基于RHEL7(CentOS7.9)部署docker环境(23.0.1、24.0.2)&#xff0c;所构建的php7.4.33镜像应用于RHEL7-9(如AlmaLinux9.1)&#xff0c;但因为docker的特性&#xff0c;适用场景是不限于此的。 文档形成时期&#xff1a;2017-2023年 因系统或软件版本不同&am…...

opencv通过轮廓点生成闭合图像

前言 有时候需要将某一些点生成闭合的二值图像。记录一下。 // 轮廓点个数 int nrCurvePoints curContour.nr; // 轮廓点 DIM2DL* curvePoints curContour.pts;std::vector<cv::Point> points; // 轮廓点集合 for (int cntPoint 0; cntPoint < nrCurvePoints; cn…...

Python 网络编程之TCP详细讲解

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

直饮水系统服务认证:提升水质与安全的必要举

直饮水系统作为一种便捷、卫生的饮水方式&#xff0c;已经越来越受到人们的欢迎。然而&#xff0c;随着市场的发展&#xff0c;直饮水系统的质量和服务也面临着一些挑战。因此&#xff0c;直饮水系统服务认证应运而生&#xff0c;成为了提升水质与安全的必要举措。 一、直饮水…...

Qt 调试系统输出报警声以及添加资源

文章目录 前言一、方法1 使用 Qsound1.添加都文件 直接报错2.解决这个错误 添加 QT multimedia3. 加入代码又遇到新的错误小结 二、第二种方法1.引入库2.添加资源2.1依次点击Qt--->Qt Resource File--->Choose2.2给资源文件起个名字&#xff0c;如&#xff1a;res&#…...

Linux下文件的创建写入读取编程

在linux下操作一个文件&#xff0c;首先要保证文件的存在&#xff08;不存在就创建&#xff09;&#xff0c;接着打开文件&#xff08;打开成功&#xff09;并得到文件描述符&#xff0c;接着在进行读写操作&#xff0c;最后还需要关闭文件。如果我们对文件进行读写之后不关闭文…...

python 解析

list(pd.DataFrame) # 所有列名切片&#xff1a;print("显式 切片:\n", df.loc[:, "number":"sum"]) 所有行&#xff0c;列是从number 到sum &#xff0c;前闭后开print("隐式 切片:\n", df.iloc[:, 1:3]) # 结果和上面一样转化成字典…...

谷歌aab包在Android 14闪退而apk没问题(targetsdk 34)

问题原因 Unity应用(target SDK 34)上线到GooglePlay&#xff0c;有用户反馈fold5设备上&#xff08;Android14系统&#xff09;疯狂闪退&#xff0c;经测试&#xff0c;在小米手机Android14系统的版本复现成功了&#xff0c;奇怪的是apk直接安装没问题&#xff0c;而打包成aa…...

34.在排序数组中查找元素的第一个和最后一个位置

34.在排序数组中查找元素的第一个和最后一个位置 给你一个按照非递减顺序排列的整数数组 nums&#xff0c;和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。 如果数组中不存在目标值 target&#xff0c;返回 [-1, -1]。 你必须设计并实现时间复杂度为…...

js树过滤

// 递归过滤得到每一项的hidden为false的数据 function filterTree(arr) { return arr.filter(item > { if (item.children) { item.children filterTree(item.children) } if (!item.hidden) { return true } }) }...

Java多线程并发篇----第十六篇

系列文章目录 文章目录 系列文章目录前言一、线程等待(wait)二、线程睡眠(sleep)三、线程让步(yield)四、线程中断(interrupt)五、Join 等待其他线程终止前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这…...

测评结果:免费的“文心一言3.5”香,但是付费的产品质量更高

文章目录 前言一、文心一言3.5生成的图片和文章1.文心一言生成的图片在文心一言3.5中输入以下内容&#xff1a;我的测评结果&#xff1a; 2.文心一言生成的文章在文心一言3.5中输入以下内容&#xff1a;我的测评结果&#xff1a; 二、ChatGPT生成的图片和文章1.ChatGPT4.0 生成…...

Matlab GUI设计基础范例(可以一步一步跟着做)

我们要做一个GUI界面&#xff0c;可以选择peaks、membrane和sinc三种三维图数据&#xff0c;选择画出surf、mesh和contour三种图像。 打开GUI 每个版本打开方式可能都不一样&#xff0c;但有一个是相同的&#xff0c;就是在命令行输入guide回车。 绘制控件 大概就绘制成这样…...

@Transactional(rollbackFor = {Exception.class})与 @Transactional区别

在Spring框架中&#xff0c;Transactional 注解用于标记方法或类&#xff0c;以表明该方法或类内包含的数据库操作应当在一个事务中执行。事务的基本原则是“原子性”&#xff0c;即所有操作要么全部成功&#xff0c;要么全部失败。 1. Transactional&#xff08;不指定 rollb…...

数据结构——二叉树(先序、中序、后序及层次四种遍历(C语言版))超详细~ (✧∇✧) Q_Q

目录 二叉树的定义&#xff1a; *特殊的二叉树&#xff1a; 二叉树的性质&#xff1a; 二叉树的声明&#xff1a; 二叉树的先序遍历&#xff1a; 二叉树的中序遍历&#xff1a; 二叉树的后序遍历&#xff1a; 二叉树的层序遍历&#xff1a; 二叉树的节点个数&#xff1a; 二叉…...

如何快速打造属于自己的接口自动化测试框架

1 接口测试 接口测试是对系统或组件之间的接口进行测试&#xff0c;主要是校验数据的交换&#xff0c;传递和控制管理过程&#xff0c;以及相互逻辑依赖关系。 接口自动化相对于UI自动化来说&#xff0c;属于更底层的测试&#xff0c;这样带来的好处就是测试收益更大&#xff…...

人工智能在数据安全中的应用场景

场景一&#xff1a;数据资产梳理 数据资产梳理是数据安全的基础。知道企业究竟有多少数据&#xff0c;这些数据在哪里&#xff1f;有哪些类型的数据&#xff1f;其中哪些是敏感数据&#xff1f;这些数据的敏感等级分别是什么&#xff1f;只有明确了保护的目标&#xff0c;才能…...

2024.1.16每日一题

LeetCode 2719.统计整数数目 2719. 统计整数数目 - 力扣&#xff08;LeetCode&#xff09; 题目描述 给你两个数字字符串 num1 和 num2 &#xff0c;以及两个整数 max_sum 和 min_sum 。如果一个整数 x 满足以下条件&#xff0c;我们称它是一个好整数&#xff1a; num1 &l…...

synchronized 学习

学习源&#xff1a; https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖&#xff0c;也要考虑性能问题&#xff08;场景&#xff09; 2.常见面试问题&#xff1a; sync出…...

Admin.Net中的消息通信SignalR解释

定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...

iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版​分享

平时用 iPhone 的时候&#xff0c;难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵&#xff0c;或者买了二手 iPhone 却被原来的 iCloud 账号锁住&#xff0c;这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...

在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module

1、为什么要修改 CONNECT 报文&#xff1f; 多租户隔离&#xff1a;自动为接入设备追加租户前缀&#xff0c;后端按 ClientID 拆分队列。零代码鉴权&#xff1a;将入站用户名替换为 OAuth Access-Token&#xff0c;后端 Broker 统一校验。灰度发布&#xff1a;根据 IP/地理位写…...

【HTML-16】深入理解HTML中的块元素与行内元素

HTML元素根据其显示特性可以分为两大类&#xff1a;块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...

基于SpringBoot在线拍卖系统的设计和实现

摘 要 随着社会的发展&#xff0c;社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 在线拍卖系统&#xff0c;主要的模块包括管理员&#xff1b;首页、个人中心、用户管理、商品类型管理、拍卖商品管理、历史竞拍管理、竞拍订单…...

【前端异常】JavaScript错误处理:分析 Uncaught (in promise) error

在前端开发中&#xff0c;JavaScript 异常是不可避免的。随着现代前端应用越来越多地使用异步操作&#xff08;如 Promise、async/await 等&#xff09;&#xff0c;开发者常常会遇到 Uncaught (in promise) error 错误。这个错误是由于未正确处理 Promise 的拒绝&#xff08;r…...

破解路内监管盲区:免布线低位视频桩重塑停车管理新标准

城市路内停车管理常因行道树遮挡、高位设备盲区等问题&#xff0c;导致车牌识别率低、逃费率高&#xff0c;传统模式在复杂路段束手无策。免布线低位视频桩凭借超低视角部署与智能算法&#xff0c;正成为破局关键。该设备安装于车位侧方0.5-0.7米高度&#xff0c;直接规避树枝遮…...

通过MicroSip配置自己的freeswitch服务器进行调试记录

之前用docker安装的freeswitch的&#xff0c;启动是正常的&#xff0c; 但用下面的Microsip连接不上 主要原因有可能一下几个 1、通过下面命令可以看 [rootlocalhost default]# docker exec -it freeswitch fs_cli -x "sofia status profile internal"Name …...

2025年低延迟业务DDoS防护全攻略:高可用架构与实战方案

一、延迟敏感行业面临的DDoS攻击新挑战 2025年&#xff0c;金融交易、实时竞技游戏、工业物联网等低延迟业务成为DDoS攻击的首要目标。攻击呈现三大特征&#xff1a; AI驱动的自适应攻击&#xff1a;攻击流量模拟真实用户行为&#xff0c;差异率低至0.5%&#xff0c;传统规则引…...