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.3 Deref trait Pt.2:隐式解引用转化与可变性

15.3.1 函数和方法的隐式解引用转化

隐式解引用转化(deref coercion)是为函数和方法提供的一种便捷特性。

它的原理是:假如类型 T 实现了 Deref trait,那么解引用转化可以把 T 的引用转换成对 T 应用 Deref 之后得到的引用。

当某个类型的引用被传给函数或方法,但其类型与声明的参数类型不匹配时,解引用转化会自动发生。编译器会对 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
    }
}
}

这是上一篇文章的代码。它定义了 MyBox 元组结构体(元组结构体的介绍详见 5.1. 定义并实例化struct),创建了 new 函数,并为其实现了 Deref trait,因此可以对 MyBox 使用普通的解引用操作。

以下是新增部分:

#![allow(unused)]
fn main() {
fn hello(name: &str) {
    println!("Hello, {}", name);
}
}

hello 函数接收 &str,也就是字符串切片,然后把它打印出来。

再看主函数:

fn main(){
    let m = MyBox::new(String::from("Rust"));
    hello(&m);
}

mMyBox<String> 类型,&m 就是 &MyBox<String>。然而 hello 期望的是 &str,但这段代码并不会报错。为什么?

首先,MyBox 已经实现了 Deref trait,所以 Rust 可以调用 deref,把 &MyBox<String> 转换成 &String。这就是刚才那个比较抽象的规则。

到这一步还没完。&String&str 是不同的类型,那又是怎么转换的呢?因为 String 也实现了 Deref trait,而且它的 deref 实现返回的是 &str 类型的字符串切片,所以 Rust 会对 &String 使用 deref,把 &String 转换成 &str。最终类型就匹配了。

如果 Rust 没有解引用转化,写法会是这样:

#![allow(unused)]
fn main() {
hello(&(*m)[..]);
}
  • 先使用解引用运算符 *,把 mMyBox<String> 转换成 String
  • 再加上引用符号 &,把 String 转换成 &String
  • 通过切片语法 [..],可以获得 String 完整内容的引用,并将其值从 &String 转换成 &str

15.3.2 解引用与可变性

可以使用 DerefMut trait 来重载可变引用的 * 运算符。与 Deref 相比,DerefMut 多了 Mut,意思是 DerefMut 返回可变引用 &mut T,而 Deref 返回不可变引用 &T

当类型和 trait 满足下列三种情况时,Rust 会执行解引用转化:

  • T: Deref<Target = U> 时,&T 可以转换成 &UT 实现了 Deref trait,且 Derefderef 的返回类型是 &U,因此 &T 可以转换成 &U。 例如,上文代码中的 MyBox 类型实现了 Deref,其 deref 方法返回 &T,所以 &MyBox 可以转换成 &T

  • T: DerefMut<Target = U> 时,&mut T 可以转换成 &mut UT 实现了 DerefMut trait(DerefMut 返回可变引用 &mut T),且 DerefMutderef 的返回类型是 &mut U,因此 &mut T 可以转换成 &mut U

  • T: Deref<Target = U> 时,&mut T 可以转换成 &U。 Rust 可以自动地把可变引用转换成不可变引用,但反过来绝对不行。把不可变引用转换成可变引用要求该引用是唯一的(借用规则中有讲,详见 4.4. 引用与借用)。