简单解释

Deref定义在**std::ops**中,这里的trait允许重写操作符的行为

Deref对应的是解引用运算符:*

解引用是指通过引用(指针)拿到其指向的值
例如:

  1. fn main() {
  2. let x = 5;
  3. let y = &x;
  4. // 这里对y解引用,拿到了它指向的值:5
  5. assert_eq!(5, *y);
  6. }

可以为自定义结构体**T**实现Deref<Target=**U**>,返回内部某个成员的引用

  • 一方面,相当于对外暴露了一个可以直接使用的内部变量
  • 另一方面,相当于把对结构体的引用(**&T**)转换为了对这个内部变量的引用(**&U**)

定义

trait定义:

  1. pub trait Deref {
  2. type Target: ?Sized;
  3. #[must_use]
  4. pub fn deref(&self) -> &Self::Target;
  5. }

一些智能指针实现了Deref trait

有些智能指针实现了Deref trait,所以可以像一般的引用(指针)一样使用解引用运算符
例如:Box<T>

  1. #[test]
  2. fn ff(){
  3. let x = Box::new(5);
  4. assert_eq!(5,*x)
  5. }

自定义智能指针实现Deref trait

对于自定义的结构体,也可以通过实现Deref trait暴露内部的值
例如:自定义类似Box的智能指针

  1. use std::ops::Deref;
  2. // 定义元组结构体存放值
  3. struct MyBox<T>(T);
  4. impl<T> MyBox<T> {
  5. fn new(x:T) -> Self {
  6. MyBox(x)
  7. }
  8. }
  9. // 为MyBox实现Deref trait
  10. impl<T> Deref for MyBox<T>{
  11. type Target = T;
  12. fn deref(&self) -> &T {
  13. // 返回元组中第一个元素的引用
  14. &self.0
  15. }
  16. }
  17. #[test]
  18. fn ff(){
  19. let x = MyBox::new(5);
  20. // 解引用操作
  21. assert_eq!(5,*x)
  22. }

在如上代码的24行,编译器内部实际上会做一个转换:*(y.deref()),也就是先获取deref中返回的引用,再对这个常规引用进行解引用

没有Deref trait的话,编译器只会解引用 & 引用(普通指针)类型。deref方法向编译器提供了获取任何实现了Deref trait的类型的值。

函数或方法中的隐式Deref强制转换

对于实现了Deref trait的类型,在给函数或方法传参时,会自动发生一系列的解引用转换,把提供的类型转换为参数需要的类型

例如:

  1. // 如下的hello方法需要&str类型的参数
  2. fn hello(name: &str) {
  3. println!("Hello, {}!", name);
  4. }
  5. fn main() {
  6. // 自定义智能指针类型
  7. let m = MyBox::new(String::from("Rust"));
  8. // 把MyBox类型的引用传给函数
  9. hello(&m);
  10. }

在如上第10行,给hello方法提供的是**&MyBox<String>**类型的引用,而hello方法需要的是**&str**类型的引用,因此会在编译时发生一系列deref调用来转换类型:

  1. Rust通过deref调用将&MyBox<String>变为&String
  2. Rust再次调用deref&String变为&str

Rust提供了**DerefMut trait**用于重载可变引用* 运算符。
Rust在发现类型和trait实现满足三种情况时会进行Deref强制转换:

  • T: Deref<Target=U> 时从 &T&U
  • T: DerefMut<Target=U> 时从 &mut T&mut U
  • T: Deref<Target=U> 时从 &mut T&U。 :从可变引用强制转换到不可变引用

使用.运算符时发生Deref强制转换

例如:

  1. #[test]
  2. fn ff(){
  3. let x = MyBox::new("abcde");
  4. // deref强制转换
  5. assert_eq!(5,x.len())
  6. }

以上第6行,对x使用.操作符时会发生deref强制转换,因此可以直接调用String类型的len方法