• Deref强制多态
    • Deref和方法调用

    Deref强制多态

    deref-coercions.md


    commit bd8d27beb54ef2a7bb4162d43006792f9ceae361

    标准库提供了一个特殊的特性,Deref。它一般用来重载*,解引用运算符:

    1. use std::ops::Deref;
    2. struct DerefExample<T> {
    3. value: T,
    4. }
    5. impl<T> Deref for DerefExample<T> {
    6. type Target = T;
    7. fn deref(&self) -> &T {
    8. &self.value
    9. }
    10. }
    11. fn main() {
    12. let x = DerefExample { value: 'a' };
    13. assert_eq!('a', *x);
    14. }

    这对编写自定义指针类型很有用。然而,有一个与Deref相关的语言功能:“解引用强制多态(deref coercions)”。规则如下:如果你有一个U类型,和它的实现Deref<Target=T>,(那么)&U的值将会自动转换为&T。这是一个例子:

    1. fn foo(s: &str) {
    2. // Borrow a string for a second.
    3. }
    4. // String implements Deref<Target=str>.
    5. let owned = "Hello".to_string();
    6. // Therefore, this works:
    7. foo(&owned);

    在一个值的前面用&号获取它的引用。所以owned是一个String&owned是一个&String,而因为impl Deref<Target=str> for String&String将会转换为&str,而它是foo()需要的。

    这就是了。这是Rust唯一一个为你进行一个自动转换的地方,不过它增加了很多灵活性。例如,Rc<T>类型实现了Deref<Target=T>,所以这可以工作:

    1. use std::rc::Rc;
    2. fn foo(s: &str) {
    3. // Borrow a string for a second.
    4. }
    5. // String implements Deref<Target=str>.
    6. let owned = "Hello".to_string();
    7. let counted = Rc::new(owned);
    8. // Therefore, this works:
    9. foo(&counted);

    我们所做的一切就是把我们的String封装到了一个Rc<T>里。不过现在我们可以传递Rc<String>给任何我们有一个String的地方。foo的签名并无变化,不过它对这两个类型都能正常工作。这个例子有两个转换:&Rc<String>转换为&String接着是&String转换为&str。只要类型匹配 Rust 将可以做任意多次这样的转换。

    标准库提供的另一个非常通用的实现是:

    1. fn foo(s: &[i32]) {
    2. // Borrow a slice for a second.
    3. }
    4. // Vec<T> implements Deref<Target=[T]>.
    5. let owned = vec![1, 2, 3];
    6. foo(&owned);

    向量可以Deref为一个切片。

    Deref和方法调用

    当调用一个方法时Deref也会出现。考虑下面的例子:

    1. struct Foo;
    2. impl Foo {
    3. fn foo(&self) { println!("Foo"); }
    4. }
    5. let f = &&Foo;
    6. f.foo();

    即便f&&Foo,而foo接受&self,这也是可以工作的。因为这些都是一样的:

    1. f.foo();
    2. (&f).foo();
    3. (&&f).foo();
    4. (&&&&&&&&f).foo();

    一个&&&&&&&&&&&&&&&&Foo类型的值仍然可以调用Foo定义的方法,因为编译器会插入足够多的*来使类型正确。而正因为它插入*,它用了Deref