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
相关的语言功能:“解引用强制多态(deref coercions)”。规则如下:如果你有一个U
类型,和它的实现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>
类型实现了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>
里。不过现在我们可以传递Rc<String>
给任何我们有一个String
的地方。foo
的签名并无变化,不过它对这两个类型都能正常工作。这个例子有两个转换:Rc<String>
转换为String
接着是String
转换为&str
。只要类型匹配Rust将可以做任意多次这样的转换。
标准库提供的另一个非常通用的实现是:
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
也会出现。考虑下面的例子:
struct Foo;
impl Foo {
fn foo(&self) { println!("Foo"); }
}
let f = &&Foo;
f.foo();