“解引用”(Deref) 是“取引用”(Ref) 的反操作。 取引用, 我们有&、 &mut等操作符, 对应的, 解引用, 我们有*操作符。 默认的“取引用”、 “解引用”操作是互补抵消的关系, 互为逆运算。
fn main() {
let v1 = 1;
let p = &v1; //引用
let v2 = *p; //解引用
println!("{} {}", v1, v2);
}
上例中, v1的类型是i32, p的类型是&i32, *p的类型又返回i32。
自动解引用虽然是编译器来做的, 但是自动解引用的行为可以由开发者来定义。
通过实现 std::ops::Deref 和 std::ops::DerefMut trait,可以修改解引用操作符 * 和 . 在自定义类型上的行为。
Deref的定义如下所示。 DerefMut的唯一区别是返回的是&mut型引用。
trait Deref {
type Target: ?Sized;
fn deref(&self) -> &Self::Target;
}
trait DerefMut: Deref {
fn deref_mut(&mut self) -> &mut Self::Target;
}
比如, 标准库中实现了Box的解引用:
impl const Deref for Box {
type Target = T;
fn deref(&self) -> &T {
&**self
}
}
我们可以使用*y提取出来Box里的值。
fn main() {
let x = 6;
let y = Box::new(x);
assert_eq!(6, x);
assert_eq!(6, *y);
//assert_eq!(6, y); error
}
请大家注意这里的类型, deref() 方法返回的类型是&Target, 而不是Target。 如果说有变量y的类型为Box, *y的类型并不等于y.deref() 的类型。 *y的类型实际上是Target, 即i32。
Rust提供的“自动解引用”机制, 是在某些场景下“隐式地”“自动地”帮我们做了一些事情。 什么是自动解引用呢?
自动deref的规则是,
fn main() {
let x = "hello world";
let y = Box::new(String::from("hello world"));
assert_eq!("hello world", x);
assert_eq!("hello world", *y);
}
Box实现了Deref trait,所以Rust可以通过调用deref来将Box
如果智能指针中的方法与它内部成员的方法冲突了怎么办呢? 编译器会优先调用当前最匹配的类型, 而不会执行自动deref, 在这种情况下, 我们就只能手动deref来表达我们的需求了。
clone方法在Rc和&str类型中都被实现了, 所以调用时会直接调用Rc的clone方法, 如果想调用Rc里面&str类型的clone方法, 则需要使用“解引用”操作符手动解引用。
fn main() {
let a = Rc::new(String::from("hello world"));
let b = a.clone();
let c = (*a).clone();
}
在match表达式中 ,引用也需要手动解引用
fn main() {
let s = String::new();
match &s {
"" => {}
_ => {}
}
}
match后面的变量类型是&String, 匹配分支的变量类型为&'static str,这种情况下就需要我们手动完成类型转换了。 手动将&String类型转换为&str类型的办法如下。
1) match s.deref()。这个方法通过主动调用deref()方法达到类型转换的目的。 此时我们需要引入Deref trait方可通过编译, 即加上代码use std::ops::Deref; 。
2) match &*s。 我们可以通过*s运算符, 也可以强制调用deref()方法, 与上面的做法一样。
3) match s.as_ref() 。 这个方法调用的是标准库中的std::convert::AsRef方法, 这个trait存在于prelude中, 无须手工引入即可使用。
4) match s.borrow()。 这个方法调用的是标准库中的std::borrow::Borrow方法。 要使用它, 需要加上代码use std::borrow::Borrow;
5) match &s[..]。 这个方案也是可以的, 这里利用了String重载的Index操作。