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相当于是一个指针。第一个断言中5和x是相等的,没有问题。y是个指针,这里是正数5的引用,它指向一个值: 5。如果想把它指向的值取出来,那就是在前面加一个解引用符号*,所以*y和5也是相等的。

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

相关推荐
code_Bo13 分钟前
不只命令行:我把跨 Agent 会话同步做成了桌面应用
前端·javascript·后端
kyriewen27 分钟前
AI 能把页面写出来,为什么改个需求就开始崩?
前端·javascript·人工智能
SomeOtherTime1 小时前
Rust 泛型与特性讲解(AI回答)
rust
涛涛ing2 小时前
2026年最值得关注的前端变化,藏在“默认值”里
前端
亿元程序员3 小时前
太牛了!这AI做出来的游戏,居然还能变现!
前端
hellice4 小时前
闪传 FlashDrop 实战:我用 300 行 Node 代码做了个「4 位取件码」临时文件中转站
前端·产品
用户0829773260054 小时前
RTX 5500 MIG 踩坑记录:切分之后模型跑不起来,排错整整一下午
前端
Lank_M4 小时前
AI 放大后的图更清楚,但它不能当原图入库
前端
Swot4 小时前
SvelteKit 数据流研究:`+page.js` 和 `+page.server.js` 到底谁在跑,数据又去了哪儿
前端·后端
用户4147679304154 小时前
Windows 触摸屏上 Swiper 轮播滑不动?可能是内部滚动容器抢走了你的手势
前端