【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方法返回一个引用,然后再使用解引用符号*进行普通的解引用操作。

相关推荐
月明长歌4 分钟前
Java多线程线程池ThreadPoolExecutor理解总结:6 个核心参数 + 4 种拒绝策略(附完整示例)
java·开发语言
学编程的小鬼11 分钟前
JVM 常见的问题
开发语言·jvm
故事不长丨14 分钟前
C#File文件操作全解析:从基础用法到异常处理
服务器·开发语言·visualstudio·c#·文件操作·io流·file
lowhot18 分钟前
C语言UI框架
c语言·开发语言·笔记·ui
大橙子打游戏23 分钟前
OpenCode真的可以挑战Claude Code
后端
苏三说技术28 分钟前
如何设计一个高并发系统?
后端
gihigo199828 分钟前
使用MATLAB绘制3D心形图和玫瑰花图案
开发语言·matlab·3d
程序员鱼皮31 分钟前
干掉 Claude Code,这个开源 AI 编程工具杀疯了?
前端·后端·计算机·ai·程序员
柠檬叶子C32 分钟前
【Python】解决 No module named ‘imp‘ 问题 | Python3 中废弃的 imp 模块
开发语言·python