【Rust自学】15.2. Deref trait Pt.1:什么是Deref、解引用运算符*与实现Deref trait
喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以关注专栏。谢谢喵!(=・ω・=)
15.2.1. 什么是Deref
trait
Deref
的全写是Dereference,就是引用的英文reference加上"de"这个反义前缀,意思为解引用。
如果一个类型实现了Deref
trait,那么它就使我们可以自定义解引用运算符*
的行为。通过实现Deref
trait,智能指针可像常规引用一样来处理。
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
:
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
这里报错了,报错信息是:
Type `MyBox<{integer}>` cannot be dereferenced
类型MyBox
不能被解引用。
这是因为我们没有为MyBox
实现Deref
trait。
15.2.5. 实现Deref
trait
标准库中的Deref
trait要求我们实现一个deref
方法:这个方法借用self
,返回一个指向内部数据的引用。
以上面的代码为例,如果想要为MyBox
实现Deref
trait(也就是实现deref
方法),要这么写:
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
trait就得写impl<T> Deref for MyBox<T>
这个impl
块。在这个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);
}
能够通过编译,没有问题。
而实际上主函数里的*y
的写法Rust编译器会隐式地展开为:
*(y.deref())
先调用了MyBox
类型上的deref
方法返回一个引用,然后再使用解引用符号*
进行普通的解引用操作。