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

相关推荐
疯狂的沙粒16 分钟前
在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?
前端·uni-app·html
小妖66619 分钟前
html 滚动条滚动过快会留下边框线
前端·html
heroboyluck33 分钟前
Svelte 核心语法详解:Vue/React 开发者如何快速上手?
前端·svelte
海的诗篇_35 分钟前
前端开发面试题总结-JavaScript篇(二)
开发语言·前端·javascript·typescript
琹箐1 小时前
ant-design4.xx实现数字输入框; 某些输入法数字需要连续输入两次才显示
前端·javascript·anti-design-vue
程序员-小李1 小时前
VuePress完美整合Toast消息提示
前端·javascript·vue.js
Uyker2 小时前
从零开始制作小程序简单概述
前端·微信小程序·小程序
EndingCoder6 小时前
React从基础入门到高级实战:React 实战项目 - 项目三:实时聊天应用
前端·react.js·架构·前端框架
阿阳微客7 小时前
Steam 搬砖项目深度拆解:从抵触到真香的转型之路
前端·笔记·学习·游戏
德育处主任Pro7 小时前
『React』Fragment的用法及简写形式
前端·javascript·react.js