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

相关推荐
Hello-Mr.Wang11 分钟前
vue3中开发引导页的方法
开发语言·前端·javascript
程序员凡尘38 分钟前
完美解决 Array 方法 (map/filter/reduce) 不按预期工作 的正确解决方法,亲测有效!!!
前端·javascript·vue.js
编程零零七4 小时前
Python数据分析工具(三):pymssql的用法
开发语言·前端·数据库·python·oracle·数据分析·pymssql
(⊙o⊙)~哦6 小时前
JavaScript substring() 方法
前端
无心使然云中漫步6 小时前
GIS OGC之WMTS地图服务,通过Capabilities XML描述文档,获取matrixIds,origin,计算resolutions
前端·javascript
Bug缔造者6 小时前
Element-ui el-table 全局表格排序
前端·javascript·vue.js
xnian_7 小时前
解决ruoyi-vue-pro-master框架引入报错,启动报错问题
前端·javascript·vue.js
麒麟而非淇淋8 小时前
AJAX 入门 day1
前端·javascript·ajax
2401_858120538 小时前
深入理解MATLAB中的事件处理机制
前端·javascript·matlab
阿树梢8 小时前
【Vue】VueRouter路由
前端·javascript·vue.js