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

Rust编程学习 - 自动解引用的用处,如何进行“解引用”(Deref) 是“取引用”(Ref) 的反操作

那么,这个bug 该如何修正呢?为什么&long类型的指针可以向&short类型赋值, 而 &Cell 类型的变量不能向&Cell 类型的变量赋值?因为对于具有内 部可变性特点的Cell 类型而言,它里面本来是要保存&long型指针的.

结果我们给了它 一 个 &short 型指针,那么在后面取出指针使用的时候,这个指针所指向的内容已经销毁, 就出现了野指针。这个bug 的解决方案是,禁止具有内部可变性的类型,针对生命周期参数 具有“协变/逆变”特性。这个功能是通过标准库中的UnsafeCell 类型实现的:

#[lang   ="unsafe_cell"]
#[stable(feature ="rust1",since ="1.0.0")]
pub struct UnsafeCell<T:?Sized>{value:T,
}

请注意这个类型上面的标记#[lang=.]。这个标记意味着这个类型是个特殊类型, 是被编译器特别照顾的类型。这个类型的说明文档需要特别提示读一下:

The   core   primitive   for   interior   mutability   in   Rust.
UnsafeCell<T>is   a   type   that   wraps   some   T   and   indicates   unsafe   interior   operations on  the  wrapped  type.Types  with  an  UnsafeCell<T>field  are  considered  to  have  an  'unsafe interior'.The   UnsafeCell<T>type    is    the    only    legal    way    to    obtain    aliasable    data    that is   considered   mutable.In   general,transmuting   an   &T   type   into   an   &mut   T   is   considered undefined    behavior.
Types like Cell<T>and RefCell<T>use this type to wrap their internal data.

所有具有内部可变性特点的类型都必须基于UnsafeCell 来实现,否则必然出现各种 问题。这个类型是唯一合法的将&T 类型转为&mut T类型的办法。绝对不允许把&T 直接转换为&mutT 而获得可变性。这是未定义行为。

大家可以自行读一下Cell 和 RefCell 的源码,可以发现,它们能够正常工作的关键 在于它们都是基于UnsafeCell 实现的,而UnsafeCell 本身是编译器特殊照顾的类型。 所以我们说“内部可变性”这个概念是Rust 语言提供的一个核心概念,而不是通过库模拟出 来 的 。

实际上,上面那个CellV2 示例也正说明了写unsafe 代码的困难之处。许多时候,我们 的确需要使用unsafe 代码来完成功能,比如调用C 代码写出来的库等。但是却有可能一不小 心违反了Rust 编译器的规则。比如,你没读过上面这段文档的话,不大可能知道简单地通过 裸指针强制类型转换实现&T 到 &mut T的类型转换是错误的。这么做会在编译器的生命周 期静态检查过程中制造出一个漏洞,而且这个漏洞用简单的测试代码测不出来,只有在某些 复杂场景下才会导致内存不安全。Rust 代码中写unsafe 代码最困难的地方其实就在这样的细 节中,有些人在没有完全理解掌握Rust 的 safe 代码和 unsafe 代码之间的界限的情况下,乱 写 unsafe 代码,这是不负责任的。本书后面还会有一章专门讲解unsafe 关键字。


“解引用”(Deref) 是“取引用”(Ref) 的反操作。

取引用,我们有&、&mut 等操作符, 对应的,解引用,我们有*操作符,跟C 语言是一样的。示例如下:

fn main(){let v1 =1;let p=&v1;// 取引用操作let v2 =*p;  / / 解引用操作println!("{}{}",v1,v2);
}

比如说,我们有引用类型p:&i32;, 那么可以用*符号执行解引用操作。上例中,v1的类型是i32,p 的类型是&i32,*p 的类型又返回i32。


自定义解引用

解引用操作可以被自定义。方法是,实现标准库中的std::ops::Deref 或 者 std::ops::DerefMut 这两个trait。Deref的定义如下所示。DerefMut的唯一区别是返回的是&mut 型引用都是类似的,因 此不过多介绍了。

pub trait Deref{type Target:?Sized;fn deref(&self)->&Self::Target; }
pub trait DerefMut:Deref{fn deref_mut(&mut self)->&mut Self::Target;
}

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

impl ops::Deref for String{
type Target =str;
#[inline]fn deref(&self)->&str{unsafe{str::from_utf8_unchecked(&self.vec)}}
}

请大家注意这里的类型,deref() 方法返回的类型是&Target, 而 不 是Target。如果说有变量s 的类型为String,*s 的类型并不等于s.deref() 的类型。

  • *s 的类型实际上是Target, 即 str 。&*s 的类型才是&str。
  • s.deref() 的类型为&Target, 即 &str。

在这里插入图片描述

以上关系有点绕。关键是要理解,*expr 的类型是Target,而 deref()方法返回的 类型却是&Target。标准库中有许多我们常见的类型实现了这个Deref 操作符。比如Vec 、String、Box、Rc、Arc 等。它们都支持“解引用”操作。从某种意义上来说,它们都 可以算做特种形式的“指针”(像胖指针一样,是带有额外元数据的指针,只是元数据不限制 在 usize 范围内了)。我们可以把这些类型都称为“智能指针”。
比如我们可以这样理解这几个类型:

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

自定义解引用操作符可以让用户自行定义各种各样的“智能指针”,完成各种各样的任 务。再配合上编译器的“自动”解引用机制,非常有用。下面我们讲解什么是“自动解引用”。

自动解引用

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

fn main(){let s ="hello";println!("length:{}",s.len());println!("length:{}",(&s).len());println!("length:{}",(&&&&&&&&&&&&&s).len());
}

编译,成功。查文档我们可以知道,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:

impl<T:?Sized>Deref for Rc<T>{
type Target =T;
#[inline(always)]
fn deref(&self)->&T{&self.inner().value}
}

它的Target 类型是它的泛型参数T 。这么设计有什么好处呢?我们看下面的用法:

use std::rc::Rc;
fn main(){let s =Rc::new(String::from("hello"));println!("{:?}",s.bytes());
}

我们创建了一个指向String 类型的Rc 指针,并调用了bytes() 方法。这里是不是 有点奇怪?这里的机制是这样的: Rc 类型本身并没有 bytes() 方法,所以编译器会尝试自动 deref, 试试s.deref().bytes()。

String 类型其实也没有bytes() 方法,但是String 可以继续deref, 于是再试试 s.deref().deref().bytes()。这次在 str 类型中找到了bytes() 方法,于是编译通过。我们实际上通过Rc 类型的变量调用了str 类型的方法,让这个智能指针透明。这就是 自 动Deref 的意义。

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

use std::rc::Rc;
use std::ops::Deref;
fn main(){let s =Rc::new(String::from("hello"));println!("length:{}",s.len());println!("length:{}",s.deref().len());println!("length:{}",s.deref().deref().len());println!("length:{}",(*s).len());println!("length:{}",(&*s).len());println!("length:{}",(&**s).len());
}

这就是为什么 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 类型的方法。

http://www.dtcms.com/a/576947.html

相关文章:

  • 云计算产品-介绍--网络/CDN篇
  • 云计算产品-介绍--安全篇
  • 3D模型骨骼绑定与动画完全指南-web平台
  • RabbitMQ 是否也支持消费组
  • 德国域名申请网站网站建设 推广薪资
  • 从零开始搭建 flask 博客实验(常见疑问)
  • 给予虚拟成像台尝鲜版十,完善支持HTML原型模式
  • ⸢ 拾叁-Ⅰ⸥⤳ 安全水位评估框架(上):威胁路径模型
  • 【Python Web开源框架】Django/Flask/FastAPI/Tornado/Pyramid
  • 拼多多seo搜索优化西安网站seo技术
  • DocxFactory: 一个C++操作word的开源库(不依赖office控件)
  • layui框架中,表单元素不显示问题
  • 主流模型调用
  • AI+XR赋能智慧研创中心:打破职业教育实训困境,推动产教深度融合
  • 网站的注册和登录怎么做军事热点最新情况
  • 在Powershell或CMD中使用conda命令
  • 体力劳动反而更难被AI取代?物联网科技如何守护最后的劳动阵地
  • 【代码审计】oasys 两处安全问题分析
  • 【IO多路转接】epoll 高性能网络编程:从底层机制到服务器实战
  • python --两个文件夹文件名比对(yolo 图和label标注比对检查)
  • 北京网站建设1000zhu建站之星模板怎么设置
  • wordpress+企业站模版做论坛app网站
  • 社群时代下的商业变革:“开源AI智能名片链动2+1模式S2B2C商城小程序”的应用与影响
  • 深入理解浏览器渲染流程:从HTML/CSS到像素的奇妙旅程
  • Photoshop - Photoshop 工具栏(24)磁性套索工具
  • 抓取QNX的RAMdump数据如何操作
  • RabbitMQ Quorum 队列与classic队列关系
  • ubuntu摄像头型号匹配不上_11-6
  • Design Compiler:时钟树在综合时的特性
  • 阿里云 icp app备案