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);
}
x是i32类型,保存的值是5;y存的是一个引用,指向x的内存地址,类型是&i32,也就是说y是x的引用。- 第一个断言把
x和5比较。由于x里存的就是5,两者相等,所以断言通过。 - 第二个断言把
*y和5比较。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;把Dereftrait 引入当前作用域。- 要为
MyBox实现Deref,就写impl<T> Deref for MyBox<T>,然后在该impl块中实现deref方法。 type Target = T;定义了Dereftrait 的关联类型。关联类型是一种稍有不同的泛型参数定义方式,以后会讲。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 方法返回一个引用,然后再使用解引用运算符 * 进行普通的解引用操作。