Appearance
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,目标类型是 T
,Rc<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官方文档或相关教程。