Learning Rust - Deref Coercions

Syntax and Semantics

Deref coercions

前回はtraitによる演算子のoverloadを扱いました。 Overloadできる演算子には、間接参照演算子*を実装する、Dereftraitがあります。 このDereftraitはユーザー定義ポインタ型の間接参照に使えますが、 すこし特別な機能も持っています。

まずは普通の使いかたから。 DerefExampleというポインタっぽい型を定義し、Dereftraitをimplします。 間接参照先の型をTargetというassociation typeに指定します。

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の何が特別かというと、次のようなルールがあります。

Uに対して、Deref<Target = T>をimplすると、&U型の値は暗黙的に&Tに変換できる。

例を見てみます。

fn foo(s: &str) {
    //
}

let owned = "Hello".to_string(); // owned: String

// String implements Deref<Target = str>
// Thus, &String will coerced to &str
foo(&owned);

StringDeref<Target = str>をimplされているので、 &Stringは暗黙的に&strに変換され、foo()に渡されます。

他には、リファレンスカウンタをもつRc<T>型は、Deref<Target = T>をimplされています。 よって、&Rc<T>型の値は暗黙的に&T型にキャストできます。

use std::rc::Rc;

fn foo(s: &str) {
    //
}

let owned = "Hello".to_string(); // owned: String
let counted = Rc::new(owned); // counted: Rc<String>

foo(&counted); // &Rc<String> -> &String -> &str

この例では、&Rc<String>から&Stringを経て&strへ、二段階のキャストが起こっています。

Deref and method calls

もう一つ、Derefには特別な機能があります。 それは、メソッドをよぶときは*を省略して、間接参照できるということです。

struct Foo;

impl Foo {
    fn foo(&self) { println!("Foo"); }
}

let f = &&Foo;

f.foo();

f&&Foo型ですが、&Fooをとるメソッドが呼べます。 これは、コンパイラが自動的に*を挿入してくれるからです。 *は必要なだけ挿入されるので、(&&&&&&&&&f).foo()みたいなのもコンパイルできます。