在Rust标准库中,存在很多常用的工具类特型,它们能帮助我们写出更具有Rust风格的代码。
你可以通过在你的类型上实现std::ops::Deref
和std::ops::DerefMut
特型来自定义解引用操作例如*
操作符和.
操作符的行为。像Box<T>
和Rc<T>
实现了这两个特型,所以他们的行为就像Rust内置指针类型一样。例如,如果你有一个Box<Complex>
类型的值b
,此时,*b
代表的是b
指向的那个复数值。b.re
代表该复数的实部.
如果对引用赋值或者借借出一个可变引用,那么Rust使用DerefMut
特型。否则,只读就足够了,此时会使用Deref
特型。
这两个特型的定义类似如下:
trait Deref {
type Target: ?Sized;
fn deref(&self) -> &Self::Target;
}
trait DerefMut: Deref {
fn deref_mut(&mut self) -> &mut Self::Target;
}
deref
和deref_mut
函数都使用&self
引用作为参数并且返回一个&Self::Target
的引用。 Target是该类型所拥有的,包含的或者指向的一个具体变量类型(显然,它不可能把它不知道的东西借出去,例如另一个结构体中的字段)。例如对Box<Complex>
来说,这里的Target
就是Complex
。注意DerefMut
拓展了Deref
,这时显而易见的,如果你能解引用并修改它,那么你肯定能借出一个共享的引用而不修改。因为函数返回的引用的生命周期和&Self
相同(生命周期三原则),只要函数返回的引用一直存在,那么Self
本身就一直被借用。其实这里说的是只要是&Self
有效,那么函数得到的引用就一直有效。
Deref
和DerefMut
特型同时还扮演了其它角色。由于deref
函数拿走了一个&Self
引用但是返回了一个&Self::Target
引用,Rust可以使用它来进行自动引用转换,将&Self
转换成&Self::Target
。换句话说,如果插入一个deref
函数能消除类型不匹配的语法错误,Rust会自动为你插入。DerefMut
也可以作相应的转换,只不过是可变引用。这个功能叫着deref coericoins
(强制解引用),一个类型被强制表现为另一种类型。
尽管你也可以自己写一个类似强制转引用的功能,但是它们很方便:
-
如果你有一个
Rc<String>
类型的值r
,你想在它上面应用String::find
函数,你可以简单的写成r.find(?)
,而不是(*r).find(?)
。这是因为这个find
函数调用隐式的借用r
, 而Rc<T>
实现了Deref<Target=T>
,因此&Rc<String>
可以被解引用为&String
.其实这里就是智能指针的应用(可以把智能指针当成普通内置指针使用) -
你可以在
String
上使用split_at
函数,虽然该函数是定义在str
字符串切片类型上的,因为String
实现了Deref<Target=str>
。String
并不需要重新实现split_at
函数,因为你可以从&String
强制解引用得到&str
。 这里其实是Rust中的一个便利性,我们可以在非常底层的数据结构上定义函数,然后其它结构可以直接使用。最常见的就是Vec<T>
,其绝大部分函数是定义在切片[T]
上的,但是因为强制解引用,你可以非常简单的直接在向量上使用这些函数。我们看一下代码直观就更明显:impl str { pub const fn len(&