解引用想必大家都很熟悉,和CPP、Go等语言类似,都是用*操作符来进行解引用,既获取引用/指针所指向的值,在Rust中可以同实现Deref trait来重载*运算符
Deref trait
*的意义
在我们使用*对y进行解引用时,编译器实际帮我们做的是,
rust
*(y.deref())
*运算符包含两个行为:一个朴素的解引用* + deref()。首先通过deref获得一个引用,再使用朴素的解引用方式取到值
Deref trait
由于在对某一个变量进行解引用时,实际编译器会自动帮我们调用deref()方法,而该方法正是属于Deref trait,其定义如下,deref方法返回的是目标类型的引用。
rust
pub trait Deref {
type Target: ?Sized;
fn deref(&self) -> &Self::Target;
}
自定义智能指针
当我们使用Box智能指针时,当对其进行解引用时,会返回其内部值,这就是通过自定义deref方法实现的
rust
// MyBox定义
struct MyBox<T>(T);
impl<T> MyBox<T> {
fn new(x: T) -> MyBox<T> {
MyBox(x)
}
}
// 重载*
use std::ops::Deref;
impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
隐式Deref转换
对于函数和方法的传参,Rust 提供了一个极其有用的隐式转换:
Deref
转换。若一个类型实现了Deref
特征,那它的引用在传给函数或方法时,会根据参数签名来决定是否进行隐式的Deref
转换
rust
fn main() {
let s = String::from("hello world");
display(&s)
}
fn display(s: &str) {
println!("{}",s);
}
String
实现了Deref
特征,可以在需要时自动被转换为&str
类型&s
是一个&String
类型,当它被传给display
函数时,自动通过Deref
转换成了&str
- 必须使用
&s
的方式来触发Deref
(仅引用类型的实参才会触发自动解引用)
连续隐式转换
Deref
可以支持连续的隐式转换,直到找到适合的形式为止:
rust
fn main() {
let s = MyBox::new(String::from("hello world"));
display(&s)
}
fn display(s: &str) {
println!("{}",s);
}
这里我们使用了之前自定义的智能指针 MyBox
,并将其通过连续的隐式转换变成 &str
类型:首先 MyBox
被 Deref
成 String
类型,结果并不能满足 display
函数参数的要求,编译器发现 String
还可以继续 Deref
成 &str
,最终成功的匹配了函数参数
引用归一化
Rust 会做隐式转换时自动把智能指针和 &&&&v
做引用归一化操作,转换成 &v
形式,最终再对 &v
进行解引用:
- 把智能指针(比如在库中定义的,Box、Rc、Arc、Cow 等)从结构体脱壳为内部的引用类型,也就是转成结构体内部的
&v
- 把多重
&
,例如&&&&&&&v
,归一成&v
看一个例子
rust
fn foo(s: &str) {}
// 由于 String 实现了 Deref<Target=str>
let owned = "Hello".to_string();
// 因此下面的函数可以正常运行:
foo(&owned);
rust
use std::rc::Rc;
fn foo(s: &str) {}
// String 实现了 Deref<Target=str>
let owned = "Hello".to_string();
// 且 Rc 智能指针可以被自动脱壳为内部的 `owned` 引用: &String ,然后 &String 再自动解引用为 &str
let counted = Rc::new(owned);
// 因此下面的函数可以正常运行:
foo(&counted);
rust
struct Foo;
impl Foo {
fn foo(&self) { println!("Foo"); }
}
let f = &&Foo;
f.foo();
(&f).foo();
(&&f).foo();
(&&&&&&&&f).foo();
三种Deref转换
在之前,我们讲的都是不可变的 Deref
转换,实际上 Rust 还支持将一个可变的引用转换成另一个可变的引用以及将一个可变引用转换成不可变的引用,规则如下:
- 当
T: Deref<Target=U>
,可以将&T
转换成&U
,也就是我们之前看到的例子 - 当
T: DerefMut<Target=U>
,可以将&mut T
转换成&mut U
- 当
T: Deref<Target=U>
,可以将&mut T
转换成&U
rust
struct MyBox<T> {
v: T,
}
impl<T> MyBox<T> {
fn new(x: T) -> MyBox<T> {
MyBox { v: x }
}
}
use std::ops::Deref;
impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.v
}
}
use std::ops::DerefMut;
impl<T> DerefMut for MyBox<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.v
}
}
fn main() {
let mut s = MyBox::new(String::from("hello, "));
display(&mut s)
}
fn display(s: &mut String) {
s.push_str("world");
println!("{}", s);
}
以上代码有几点值得注意:
- 要实现
DerefMut
必须要先实现Deref
特征:pub trait DerefMut: Deref
T: DerefMut<Target=U>
解读:将&mut T
类型通过DerefMut
特征的方法转换为&mut U
类型,对应上例中,就是将&mut MyBox<String>
转换为&mut String
对于上述三条规则中的第三条,它比另外两条稍微复杂了点:Rust 可以把可变引用隐式的转换成不可变引用,但反之则不行