Skip to content

16.3 自动解引用的用处

用 Rc 这个“智能指针”举例。Rc 实现了 Deref:


rust
impl<T: ?Sized> Deref for Rc<T> {
    type Target = T;

    #[inline(always)]
    fn deref(&self) -> &T {
        &self.inner().value
    }
}

它的 Target 类型是它的泛型参数 T。这么设计有什么好处呢?我们看下面的用法:


rust
use std::rc::Rc;

fn main() {
    let s = Rc::new(String::from("hello"));
    println!("{:?}", s.bytes());
}

我们创建了一个指向 String 类型的 Rc 指针,并调用了bytes()方法。这里是不是有点奇怪?

这里的机制是这样的:Rc 类型本身并没有bytes()方法,所以编译器会尝试自动 deref,试试s.deref().bytes()

String 类型其实也没有bytes()方法,但是 String 可以继续 deref,于是再试试s.deref().deref().bytes()

这次在 str 类型中找到了bytes()方法,于是编译通过。

我们实际上通过 Rc 类型的变量调用了 str 类型的方法,让这个智能指针透明。这就是自动 Deref 的意义。

实际上以下写法在编译器看起来是一样的:


rust
use std::rc::Rc;
use std::ops::Deref;

fn main() {
    let s = Rc::new(String::from("hello"));

    println!("length: {}", s.len());
    println!("length: {}", s.deref().len());
    println!("length: {}", s.deref().deref().len());

    println!("length: {}", (*s).len());
    println!("length: {}", (&*s).len());
    println!("length: {}", (&**s).len());
}

这就是为什么 String 需要实现 Deref trait,是为了让&String 类型的变量可以在必要的时候自动转换为&str类型。所以 String 类型的变量可以直接调用 str 类型的方法。比如:


rust
let s = String::from("hello");
let len = s.bytes();

虽然 s 的类型是 String,但它在调用bytes()方法的时候,编译器会自动查找并转换为s.deref().bytes()调用。所以 String 类型的变量就可以直接调用 str 类型的方法了。

同理:Vec<T>类型也实现了 Deref trait,目标类型是[T]&Vec<T>类型的变量就可以在必要的时候自动转换为&[T]数组切片类型;Rc<T>类型也实现了 Deref trait,目标类型是TRc<T>类型的变量就可以直接调用 T 类型的方法。

注意:&*两个操作符连写跟分开写是不同的含义。以下两种写法是不同的:


rust
fn joint() {
    let s = Box::new(String::new());
    let p = &*s;
    println!("{} {}", p, s);
}

fn separate() {
    let s = Box::new(String::new());
    let tmp = *s;
    let p = &tmp;
    println!("{} {}", p, s);
}

fn main() {
    joint();
    separate();
}

fn joint()是可以直接编译通过的,而fn separate()是不能编译通过的。因为编译器很聪明,它看到&*这两个操作连在一起的时候,会直接把&*s表达式理解为s.deref(),这时候 p 只是 s 的一个借用而已。而如果把这两个操作分开写,会先执行*s把内部的数据 move 出来,再对这个临时变量取引用,这时候 s 已经被移走了,生命周期已经结束。

同样的,let p = &{*s};这种写法也编译不过。这个花括号的存在创建了一个临时的代码块,在这个临时代码块内部先执行解引用,同样是 move 语义。

从这里我们也可以看到,默认的“取引用”、“解引用”操作是互补抵消的关系,互为逆运算。 但是,在 Rust 中,只允许自定义“解引用”,不允许自定义“取引用”。 如果类型有自定义“解引用”,那么对它执行“解引用”和“取引用”就不再是互补抵消的结果了。 先&*以及先*&的结果是不同的。

Released under the MIT License