15.3 Deref trait Pt.2:隐式解引用转化与可变性
15.3.1 函数和方法的隐式解引用转化
隐式解引用转化(deref coercion)是为函数和方法提供的一种便捷特性。
它的原理是:假如类型T实现了Dereftrait,那么解引用转化可以把T的引用转换成对T应用Deref之后得到的引用。
当某个类型的引用被传给函数或方法,但其类型与声明的参数类型不匹配时,解引用转化会自动发生。编译器会对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 } }这是上一篇文章的代码。它定义了MyBox元组结构体(元组结构体的介绍详见 5.1. 定义并实例化struct),创建了new函数,并为其实现了Dereftrait,因此可以对MyBox使用普通的解引用操作。
以下是新增部分:
fn hello(name: &str) { println!("Hello, {}", name); }hello函数接收&str,也就是字符串切片,然后把它打印出来。
再看主函数:
fn main(){ let m = MyBox::new(String::from("Rust")); hello(&m); }m是MyBox<String>类型,&m就是&MyBox<String>。然而hello期望的是&str,但这段代码并不会报错。为什么?
首先,MyBox已经实现了Dereftrait,所以 Rust 可以调用deref,把&MyBox<String>转换成&String。这就是刚才那个比较抽象的规则。
到这一步还没完。&String和&str是不同的类型,那又是怎么转换的呢?因为String也实现了Dereftrait,而且它的deref实现返回的是&str类型的字符串切片,所以 Rust 会对&String使用deref,把&String转换成&str。最终类型就匹配了。
如果 Rust 没有解引用转化,写法会是这样:
hello(&(*m)[..]);- 先使用解引用运算符
*,把m从MyBox<String>转换成String。 - 再加上引用符号
&,把String转换成&String。 - 通过切片语法
[..],可以获得String完整内容的引用,并将其值从&String转换成&str。
15.3.2 解引用与可变性
可以使用DerefMuttrait 来重载可变引用的*运算符。与Deref相比,DerefMut多了Mut,意思是DerefMut返回可变引用&mut T,而Deref返回不可变引用&T。
当类型和 trait 满足下列三种情况时,Rust 会执行解引用转化:
- 当T: Deref<Target = U>时,&T可以转换成&U:T实现了Dereftrait,且Deref下deref的返回类型是&U,因此&T可以转换成&U。
例如,上文代码中的MyBox类型实现了Deref,其deref方法返回&T,所以&MyBox可以转换成&T。
当
T: DerefMut<Target = U>时,&mut T可以转换成&mut U。T实现了DerefMuttrait(DerefMut返回可变引用&mut T),且DerefMut下deref的返回类型是&mut U,因此&mut T可以转换成&mut U。当
T: Deref<Target = U>时,&mut T可以转换成&U。
Rust 可以自动地把可变引用转换成不可变引用,但反过来绝对不行。把不可变引用转换成可变引用要求该引用是唯一的(借用规则中有讲,详见 4.4. 引用与借用)。