它乱就乱在语法糖!
有两种 Deref 解引用方式,一种是编译器的,一种是 Deref trait。
一、明确的 Deref trait
实现了 Deref trait 的类型,一个源类型,只能实现一个 Deref trait,即只能向一种目标类型解引用。
实际上就是找它身上有没有对应方法,比如 &String 到 &str。
只会解引用类型 ,而不会解值类型。比如 String 是无法自动解引到 &str 的, 必须是 &String 向 &str 解引。
php
fn hello(&str) {};
hello(String::from("hello")); // 是不行的,它不会自动解引用
rust
impl Deref for String {
type Target = str; // 唯一目标类型
fn deref(&self) -> &str { // 参数是 &String,返回值是 &str
// 底层逻辑:根据 String 的指针和长度,组装出一个 &str
}
}
let s = String::from("hello world");
let b = &s; // &String 类型
// 这个 starts_with 实际上是 &str 的方法,自动 deref 了
// &String -> String -> &str 这个顺序去找 starts_with 方法
b.starts_with("hello");
二、点号的魔法
点号 . 的魔法 (方法决议 ),这是写在编译器里的,它也涉及到 deref,按以下步骤来
-
如果 T 是引用(如
&U), 解引用后*&U得到U,尝试匹配方法 -
尝试当前类型的
self,&self,&mut self,在进入下一层Deref之前,编译器会把当前类型这一层的所有可能性穷尽比如其他
trait有没有实现这个方法 -
使用
Deref trait,用目标类型,从第2步骤循环尝试如
String被解成str,依次尝试str,&str,&mut str会循环解引么?让类型
A的Target是B,同时让类型B的Target又是A。可以写出来,不过过不了编译,当解引用了 64 层,编译器就会报错
例子:
- 自动解引用
rust
let n:i32 = 1;
(&n).abs(); // 这里的 . 触发了 deref,源类型是 &i32,先尝试剥离 &,发现是 i32,就能找到 abs 了
- String 的例子
rust
String::from("hello world").starts_with("hello"); // 这样实现了 String 到 &str 的转变
// 如下过程
receiver = String
self ✗ starts_with(String)
&self ✗ starts_with(&String)
&mut self ✗ starts_with(&mut String)
↓ Deref
receiver = str
self ✗ starts_with(str)
&self ✓ starts_with(&str),停止查找
&mut self (不会再检查)
- 自己实现
Deref Trait,.会一级一级自动解下去,直到最后
rust
struct InnerRef<T>(T); // 这个其实是在栈上,主要为了了解 deref,这里不关心
// 定义 deref
impl<T> std::ops::Deref for InnerRef<T> {
type Target = T;
// &self => self: &Self
fn deref(&self) -> &T {
// 参数要是 &self 的
&self.0 // &(self.0)
}
}
let m = InnerRef(InnerRef(InnerRef(InnerRef(1i32)))); // 这里写上 i32 类型,不然不了解最终类型是啥,到底有没有 abs() 方法
assert_eq!(****m - 1, 0); // 需要手动解引用
assert_eq!(m.abs() - 1, 0); // 这个可以直接解到 1i32
assert_eq!((&m).abs() - 1, 0); // 这个一样
Box的例子
rust
#[stable(feature = "rust1", since = "1.0.0")]
impl<T: ?Sized, A: Allocator> Deref for Box<T, A> {
type Target = T;
fn deref(&self) -> &T {
&**self // (self: &Self) => (*self: Self) => (*Self: T) => &T (目标的引用,即 &Box<T> => &T)
}
}
什么时候触发解引用
- 显式使用
*操作符:当你对引用或智能指针(如Box,Rc)使用*时,只生效一次 - 方法调用(
.操作符),会一直deref下去 - 函数参数传递(隐式解引用强制多态,
Deref Coercion):它也会一直deref下去