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 中,只允许自定义“解引用”,不允许自定义“取引用”。 如果类型有自定义“解引用”,那么对它执行“解引用”和“取引用”就不再是互补抵消的结果了。 先 &* 以及先 *& 的结果是不同的。

“Deref coercion”是Rust编程语言中的一个概念,涉及到Rust中的强制隐式转换和Deref trait。

首先,我们来理解“Deref”和“coercion”这两个词:

Deref:在Rust中,Deref是deref操作符*的trait。一般来说,*v操作是&v的反向操作,即尝试从资源的引用获取到资源的拷贝(如果资源类型实现了Copy),或所有权(资源类型没有实现Copy)。 Coercion:这个词在英语中意为“强制”,通常用来描述一种强制行为或手段。在Rust编程的上下文中,特别是与Deref结合使用时,它指的是一种隐式类型转换或强制类型转换的机制。

接下来,我们来看“Deref coercion”在Rust中的具体含义:

在Rust中,如果类型T实现了Deref trait,并指定了目标类型U(即T: Deref<Target=U>),那么Rust编译器会在执行*v操作时,自动先将v进行引用归一化操作,即转换为内部通用引用的形式&v。然后,由于T实现了Deref trait,编译器会自动将&v(类型为&T)转换为&U。这就是所谓的“Deref coercion”。

简而言之,当Rust编译器看到一个对Deref trait实例的解引用操作时,它会尝试隐式地将该解引用转换为Deref trait指定的目标类型的引用。这种隐式转换或强制类型转换就是“Deref coercion”。

以上信息仅供参考,如需了解更多关于Rust编程语言或Deref coercion的详细信息,建议查阅Rust官方文档或相关教程。

Released under the MIT License