实现 Deref trait 允许我们重载 解引用运算符 (dereference operator) * (与乘法运算符或通配符相区别)。通过这种方法实现Deref trait 的智能指针可以被当作常规引用来对待,可以编写操作引用的代码并用于智能指针。
通过解引用运算符追踪指针的值
常规引和是一个指针类型,一种理解指针的方式是将其看成指向储存在其他某处值的箭头。在以下例子中,创建了一个i32值的引用,接着使用解引用运算符来跟踪所引用的数据:
let x = 5; let y = &x; assert_eq!(5,x); assert_eq!(5,*y);
变量x存放了一个i32值5。y等于x的一个引用。可以断言x等于5.。然而,如果希望对y的值做出断言,必须使用 *y 来追踪引用所指向的值(也就是 解引用)。一旦解引用了y,就可以访问y所指向的整型值并可以与5做比较。
相反如果尝试编写 assert_eq!(5,y),则会得到如下编译错误:
error[E0277]: can't compare `{integer}` with `&{integer}` --> src/main.rs:31:5 | 31 | assert_eq!(5,y); | ^^^^^^^^^^^^^^^^ no implementation for `{integer} == &{integer}` | = help: the trait `PartialEq<&{integer}>` is not implemented for `{integer}` = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
不允许比较数字的引用与数字,因为它们是不同的类型。必须使用解引用运算符追踪引用所指向的值。
像引用一样使用Box<T>
可以使用Box<T>代替引用来重写以上的示例代码,解引用运算符也一样能工作,如下所示:
let x = 5; let y = Box::new(x); assert_eq!(5,x); assert_eq!(5,*y);
在Box<i32>上使用解引用运算符。
自定义智能指针
为了体会默认情况下智能指针与引用的不同,让我们创建一个类似于标准库提供的Box<T>类型的智能指针。
从根本上说,Box<T>被定义为包含一个元素的元组结构体,所以在以下的一个示例中以相同的方式定义了MyBox<T>类型。我们还定义了new函数来对应定义于Box<T>的new函数:
struct MyBox<T>(T); impl<T> MyBox<T> { fn new(x:T)->MyBox<T>{ MyBox(x) } }
这里定义了一个结构体MyBox并声明了一个泛型参数T,因为我们希望其可以存放任何类型的值。MyBox是一个包含T类型元素的元组结构体。
MyBox::new函数获取一个T类型的参数并返回一个存放传入值的MyBox实例。我们修改下之前的代码,使用MyBox<T>类型代替Box<T>,示例代码不能编译,因为Rust不知道如何解引用MyBox:
let x = 5; let y = MyBox::new(x); assert_eq!(5, x); assert_eq!(5, *y);
得到的编译错误是:
error[E0614]: type `MyBox<{integer}>` cannot be dereferenced --> src/main.rs:32:19 | 32 | assert_eq!(5, *y); | ^^
MyBox<T>类型不能解引用,因为我们尚没在该类型实现这个功能。为了启用 * 运算符的解引用功能,需要实现 Deref trait。
通过实现Deref trait 将某类型像引用一样处理
为了实现trait,需要提供trait所需的方法实现。Deref trait,由标准库提供,要求实现名为deref的方法,其借用self并返回一个内部数据的引用。MyBox<T>上的Deref实现:
impl<T> Deref for MyBox<T> { type Target = T; fn deref(&self) -> &T { &self.0 } }
type Targe = T; 语法定义了用于此trait的关联类型。关联类型是一个稍有不同的定义泛型参数的方式。
deref方法体中写入了 &self.0, 这样deref返回了我希望通过 * 运算符访问的值的引用。