Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

15.2 Deref trait Pt.1:什么是Deref、解引用运算符 与实现Deref trait

15.2.1 什么是 Deref trait

Deref 是 Dereference(解引用)的缩写。

如果一个类型实现了 Deref trait,它就允许我们自定义解引用运算符 * 的行为。通过实现 Deref智能指针可以像常规引用一样被处理

15.2.2 解引用运算符

首先强调一下:常规引用也是一种指针。看个例子:

fn main(){
	let x = 5;
	let y = &x;

	assert_eq!(x, 5);
	assert_eq!(*y, 5);
}
  • xi32 类型,保存的值是 5y 存的是一个引用,指向 x 的内存地址,类型是 &i32,也就是说 yx 的引用。
  • 第一个断言把 x5 比较。由于 x 里存的就是 5,两者相等,所以断言通过。
  • 第二个断言把 *y5 比较。y 是一个指针,如果想取出它指向的值,就在变量名前加解引用符号 *。也就是说,y 的类型是 &i32*y 的类型是 i32,由于 5 也是 i32 类型,所以 *y 可以与 5 比较,而 y 不行。

15.2.3 使用 Box<T> 当作引用

Box<T> 可以替代上例中的引用。看:

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

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

需要注意的是,上一段代码和这段代码的逻辑略有不同:

  • 上例中的 y = &x 是把一个指向 x 的指针赋给 y,这是一个指向栈内存的指针,因为 i32 存储在栈上。
  • 这里的 y = Box::new(x) 是把 x 的值复制到堆上,然后把指向该堆值的指针传给 y

15.2.4 定义自己的智能指针

Box<T> 被定义为拥有一个元素的 tuple struct(元组结构体,详见 5.1. 定义并实例化struct)。我们来定义一个 MyBox<T>,也是一个 tuple struct

#![allow(unused)]
fn main() {
struct MyBox<T>(T);

impl<T> MyBox<T> {
	fn new(x: T) -> MyBox<T> {
		MyBox(x)
	}
}
}
  • 首先定义一个元组结构体 MyBox,使用泛型参数 T 代替具体类型,并在这个元组结构体中存储一个类型为 T 的值。
  • 然后通过 impl 块定义一个 new 函数,用于创建新的 MyBox 实例。

再写主函数,看看实际使用有没有问题:

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

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

最后一个断言 assert_eq!(*y, 5)*y 处报错。报错信息是:

error[E0614]: type `MyBox<{integer}>` cannot be dereferenced
  --> src/main.rs:14:13
   |
14 |     assert_eq!(*y, 5);
   |                ^^ can't be dereferenced

For more information about this error, try `rustc --explain E0614`.
error: could not compile `mybox` (bin "mybox") due to 1 previous error

也就是说 MyBox 不能被解引用。

这是因为我们还没有为 MyBox 实现 Deref trait。

15.2.5 实现 Deref trait

标准库中的 Deref trait 要求我们实现一个 deref 方法:这个方法借用 self,并返回指向内部数据的引用。

以上面的代码为例,如果想为 MyBox 实现 Deref trait——也就是实现 deref 方法——可以这样写:

#![allow(unused)]
fn main() {
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
    }
}
}
  • use std::ops::Deref;Deref trait 引入当前作用域。
  • 要为 MyBox 实现 Deref,就写 impl<T> Deref for MyBox<T>,然后在该 impl 块中实现 deref 方法。
  • type Target = T; 定义了 Deref trait 的关联类型。关联类型是一种稍有不同的泛型参数定义方式,以后会讲。
  • deref 方法借用 self,也就是 &self,并返回类型为 &T 的引用,具体来说就是 &self.0:以引用形式返回元组结构体中索引为 0 的元素(在这个例子中其实只有一个元素)。正因为返回的是引用,所以我们可以使用 * 解引用运算符来访问这个值。

再写主函数运行一下,看看有没有问题:

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

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

能够通过编译,没有问题。

实际上,Rust 编译器会把 main 里的 *y 隐式展开为:

#![allow(unused)]
fn main() {
*(y.deref())
}

它先调用 MyBox 上的 deref 方法返回一个引用,然后再使用解引用运算符 * 进行普通的解引用操作。