deref-coercions.md
commit 024aa9a345e92aa1926517c4d9b16bd83e74c10d
標準庫提供了一個特殊的特性,Deref
。它一般用來重載*
,解引用運算符:
use std::ops::Deref;
struct DerefExample<T> {
value: T,
}
impl<T> Deref for DerefExample<T> {
type Target = T;
fn deref(&self) -> &T {
&self.value
}
}
fn main() {
let x = DerefExample { value: 'a' };
assert_eq!('a', *x);
}
這對編寫自定義指針類型很有用。然而,有一個與Deref
相關的語言功能:“解引用強制多態(tài)(deref coercions)”。規(guī)則如下:如果你有一個U
類型,和它的實現(xiàn)Deref<Target=T>
,(那么)&U
的值將會自動轉換為&T
。這是一個例子:
fn foo(s: &str) {
// borrow a string for a second
}
// String implements Deref<Target=str>
let owned = "Hello".to_string();
// therefore, this works:
foo(&owned);
在一個值的前面用&
號獲取它的引用。所以owned
是一個String
,&owned
是一個&String
,而因為impl Deref<Target=str> for String
,&String
將會轉換為&str
,而它是foo()
需要的。
這就是了。這是Rust唯一一個為你進行一個自動轉換的地方,不過它增加了很多靈活性。例如,Rc<T>
類型實現(xiàn)了Deref<Target=T>
,所以這可以工作:
use std::rc::Rc;
fn foo(s: &str) {
// borrow a string for a second
}
// String implements Deref<Target=str>
let owned = "Hello".to_string();
let counted = Rc::new(owned);
// therefore, this works:
foo(&counted);
我們所做的一切就是把我們的String
封裝到了一個Rc<T>
里。不過現(xiàn)在我們可以傳遞Rc<String>
給任何我們有一個String
的地方。foo
的簽名并無變化,不過它對這兩個類型都能正常工作。這個例子有兩個轉換:Rc<String>
轉換為String
接著是String
轉換為&str
。只要類型匹配Rust將可以做任意多次這樣的轉換。
標準庫提供的另一個非常通用的實現(xiàn)是:
fn foo(s: &[i32]) {
// borrow a slice for a second
}
// Vec<T> implements Deref<Target=[T]>
let owned = vec![1, 2, 3];
foo(&owned);
向量可以Deref
為一個切片。
Deref
和方法調用當調用一個方法時Deref
也會出現(xiàn)。考慮下面的例子:
struct Foo;
impl Foo {
fn foo(&self) { println!("Foo"); }
}
let f = &&Foo;
f.foo();
即便f
是&&Foo
,而foo
接受&self
,這也是可以工作的。因為這些都是一樣的:
f.foo();
(&f).foo();
(&&f).foo();
(&&&&&&&&f).foo();
一個&&&&&&&&&&&&&&&&Foo
類型的值仍然可以調用Foo
定義的方法,因為編譯器會插入足夠多的*
來使類型正確。而正因為它插入*
,它用了Deref
。