【Rust自学】15.2. Deref trait Pt.1:什么是Deref、解引用运算符*与实现Deref trait

喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以关注专栏。谢谢喵!(=・ω・=)

15.2.1. 什么是Deref trait

Deref的全写是Dereference,就是引用的英文reference加上"de"这个反义前缀,意思为解引用

如果一个类型实现了Deref trait,那么它就使我们可以自定义解引用运算符*的行为 。通过实现Deref trait,智能指针可像常规引用一样来处理

15.2.2. 解引用运算符

首先强调一下,常规的引用它也是一种指针。看个例子:

rust 复制代码
fn main(){
	let x = 5;
	let y = &x;

	assert_eq!(x, 5);
	assert_eq!(*y, 5);
}
  • xi32类型,值为5;y存的是一个引用,指向x的内存地址,类型是&i32,也就是说,yx的引用。
  • 第一个断言把x和5比较,由于x里存的就是5,两者相等,所以程序会通过这个断言
  • 第二个断言把*y和5比较。y是个指针,指向一个值如果想把它指向的值取出来就是在变量名前加解引用符号* 。也就是说,y的类型是&i32*y的类型是i32,由于5也是i32类型,所以*y就可以与5比较而y不行。

15.2.3. 使用Box<T>当作引用

Box<T>可以替代上例中的引用,看个例子:

rust 复制代码
fn main(){
	let x = 5;
	let y = Box::new(x);

	assert_eq!(x, 5);
	assert_eq!(*y, 5);
}

这里需要注意的是,上文的代码例和这个代码例的逻辑有点不一样:

  • 上文的y = &x是把一个指向x的指针赋给了y,是一个指向栈内存的指针(因为i32存储在栈内存中)
  • 这里的y = Box::new(x)是把x的值复制一份放到堆内存中,然后把指向堆内存中这个值的指针传给y

15.2.4. 定义自己的智能指针

Box<T>被定义为拥有一个元素的tuple struct(元组结构体,详见 5.1. 定义并实例化struct)。我们来定义一个MyBox<T>,也是一个tuple struct

rust 复制代码
struct MyBox<T>(T);

impl<T> MyBox<T> {
	fn new(x: T) -> MyBox<T> {
		MyBox(x)
	}
}
  • 首先定义了一个元组结构体MyBox,使用泛型参数T来代替实际的类型,在这个元组结构体中存储一个类型为T的值。
  • 然后通过impl块定义了一个new函数用于创建新的MyBox实例

再写主函数看看实际使用有没有问题:

rust 复制代码
fn main(){
	let x = 5;
	let y = MyBox::new(x);

	assert_eq!(x, 5);
	assert_eq!(*y, 5);
}

最后一个断言assert_eq(*y, 5)*y这里报错了,报错信息是:

复制代码
Type `MyBox<{integer}>` cannot be dereferenced

类型MyBox不能被解引用。

这是因为我们没有为MyBox实现Deref trait

15.2.5. 实现Deref trait

标准库中的Deref trait要求我们实现一个deref方法:这个方法借用self,返回一个指向内部数据的引用。

以上面的代码为例,如果想要为MyBox实现Deref trait(也就是实现deref方法),要这么写:

rust 复制代码
use std::ops::Deref;  
  
struct MyBox<T>(T);  
  
impl<T> MyBox<T> {  
    fn new(x: T) -> MyBox<T> {  
        MyBox(x)  
    }  
}  
  
impl<T> Deref for MyBox<T> {  
    type Target = T;  
    fn deref(&self) -> &T {  
        &self.0  
    }  
}
  • use std::ops::Deref;就是把Deref trait引入到当前作用域。
  • MyBox实现Deref trait就得写impl<T> Deref for MyBox<T>这个impl块。在这个impl块下面实现deref方法。
  • type Target = T;这种语法定义了Deref trait的关联类型。关联类型是一种稍有不同的泛型参数定义方式,以后会讲。
  • deref方法借用self,也就是&self,返回T类型的值,具体来说就是&self.0:把元组结构体的索引位置在0的元素,也就是第一个元素以引用的形式返回(其实本身也就只有一个元素)。正由于返回的是引用,所以我们可以使用*解引用运算符来访问这个值。

写主函数运行一下看看有没有问题:

rust 复制代码
fn main(){
	let x = 5;
	let y = MyBox::new(x);

	assert_eq!(x, 5);
	assert_eq!(*y, 5);
}

能够通过编译,没有问题。

而实际上主函数里的*y的写法Rust编译器会隐式地展开为:

rust 复制代码
*(y.deref())

先调用了MyBox类型上的deref方法返回一个引用,然后再使用解引用符号*进行普通的解引用操作。

相关推荐
陈皮话梅糖@1 小时前
使用 Provider 和 GetX 实现 Flutter 局部刷新的几个示例
开发语言·javascript·flutter
hvinsion2 小时前
基于PyQt5的自动化任务管理软件:高效、智能的任务调度与执行管理
开发语言·python·自动化·自动化任务管理
Aphelios3802 小时前
Java全栈面试宝典:线程机制与Spring IOC容器深度解析
java·开发语言·jvm·学习·rbac
qq_529835353 小时前
装饰器模式:如何用Java打扮一个对象?
java·开发语言·装饰器模式
日暮南城故里3 小时前
Java学习------源码解析之StringBuilder
java·开发语言·学习·源码
Vitalia3 小时前
从零开始学Rust:枚举(enum)与模式匹配核心机制
开发语言·后端·rust
飞飞翼4 小时前
python-flask
后端·python·flask
双叶8364 小时前
(C语言)虚数运算(结构体教程)(指针解法)(C语言教程)
c语言·开发语言·数据结构·c++·算法·microsoft
草捏子5 小时前
最终一致性避坑指南:小白也能看懂的分布式系统生存法则
后端
一个public的class5 小时前
什么是 Java 泛型
java·开发语言·后端