47_Deref Trait

1. 概述

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

2. 解引用运算符

常规的引用也是一种指针,我们先看一个示例

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

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

x存一个i32类型的整数,y是x的引用,所以y相当于是一个指针。第一个断言中5x是相等的,没有问题。y是个指针,这里是正数5的引用,它指向一个值: 5。如果想把它指向的值取出来,那就是在前面加一个解引用符号*,所以*y5也是相等的。

3. 使用Box代替上例中的引用

我门可以原因的引用换成Box<T>,如下代码

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

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

以上代码也没有任何问题,所以说Box<T>可以像引用一样来处理。

4. 定义自己的智能指针

Box<T>被定义成一个拥有元素的tuple struct,下面我们定义一个MyBox<T>,它也是一个tuple struct,即元组结构体。如下代码

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

impl<T> MyBox<T> {
    fn new(x:T) -> MyBox<T> {
        MyBox(x)
    }
}

MyBox实际上是一个有名称的元组,该元组只有一个元素。不过如果我们用于替代Box,是不能实现的,它将不能为解引用。如果需要能够解引用,我门需要实现Deref Trait。

5. 实现Deref Trait

标准库中的Deref trait要求我们实现一个deref的方法,该方法会借用self,并返回一个指向内部数据的引用。如下示例代码

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
    }
}

fn main() {
    let x = 5;
    let y = MyBox::new(x);

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

因为MyBox<T>实现了Deref trait,所以使用*可以进行解引用。实际上,*y就等同于*(y.deref()),因为rust编译器在编译时会将*y会隐式地展开成*(y.deref())

6. 函数和方法的隐式解引用转化(Deref Coerion)

隐式解引用转化(Deref Coercion)是为函数和方法提供的一种便捷特性。假设T实现了Deref trait,Deref Coerion可以把T的引用转化为T经过Deref操作后的生成引用。

当把某类型的引用传递给函数或方法时,但它的类型与定义的参数不匹配,Deref Coerion就会自动发生。编译器会对deref进行一系列调用,来把它转为所需的参数类型。这个操作在编译时完成,没有额外的性能开销。我们在5中的示例代码中追加一个函数,如下

rust 复制代码
fn hello(name: &str) {
    println!("Hello, {}", name);
}

接着在main函数中调用

rust 复制代码
fn main() {
    let m = MyBox::new(String::from("Rust"));

    hello("Rust");
    hello(&m);
}

在上面的代码中,hello方法需要接收一个字符串切片。mMyBox<String>的一个引用,由于MyBox<T>实现了deref trait,所以rust可以调用deref方法把MyBox<String>的引用转化为String的引用。而String也实现了deref trait,它的实现是返回字符串切片。所以上面hello方法传入的&m经过隐式解引用转化之后,类型就匹配了。

如果rust没有实现隐式解引用,我们的调用方法如下

rust 复制代码
hello(&(*m)[..]);

充满了各种符号,难以阅读。 所以说只要这个类型实现了Deref trait,rust就会自动分析类型,并不断尝试调用deref方法,来让它与函数或是方法定义的参数类型匹配。而且这个过程是在编译的时候完成的,对程序的运行时不会额外的性能开销。

7. 解引用与可变性

可使用Deref Mut trait重载可变引用*运算符。在类型和trait在下列三种情况发生时,rust会执行defref coercion:

  • T实现了Deref<target=U>,允许&T转为&U
  • T实现了DerefMut<target=U>,允许&mut T转为&mut U
  • T实现了Deref<target=U>,允许&mut T转为&U

rust可以把一个可变引用转为不可变引用,但是反过来不行。因为将不可变引用转为可变引用,借用规则要求这个引用必须是唯一的,但这点无法保证。上面列出的情况稍微记一下就行,这里还有些知识未涉及到。

相关推荐
IT_陈寒21 分钟前
Vite的public文件夹放静态资源?这坑我替你踩了
前端·人工智能·后端
涵涵(互关)34 分钟前
GoView各项目文件中的相关语法2
前端·javascript·vue.js
子兮曰41 分钟前
别让爬虫白嫖你的导航站了:纯免费,手把手实现加密字体防爬
前端·javascript·后端
小村儿1 小时前
连载06 - Hooks 源码深度解析:Claude Code 的确定性自动化体系
前端·后端·ai编程
心中无石马1 小时前
uniapp引入tailwindcss4.x
前端·css·uni-app
焰火19992 小时前
[Vue]可重置的响应式状态reactive
前端·vue.js
陆枫Larry2 小时前
CSS transform scale:图片放大效果背后的原理
前端
老王以为2 小时前
为什么 React 和 Vue 不一样?
前端·vue.js·react.js
web打印社区2 小时前
2026最新Web静默打印解决方案,无插件无预览,完美替代Lodop
前端·javascript·vue.js·electron·pdf
这个DBA有点耶2 小时前
分组排名不用窗口函数?那你还在写几十行的子查询
前端·代码规范