跳转至

Deref Trait

一、概述

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

二、解引用运算符

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

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也是相等的。

三、使用Box代替上例中的引用

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

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

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

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

四、定义自己的智能指针

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

struct MyBox<T>(T);

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

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

五、实现Deref Trait

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

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())

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

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

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

fn hello(name: &str) {
    println!("Hello, {}", name);
}

接着在main函数中调用

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没有实现隐式解引用,我们的调用方法如下

hello(&(*m)[..]);

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

七、解引用与可变性

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