`Deref` による型強制

標準ライブラリは特別なトレイト Deref を提供します。 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); }
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 に関連する機能がもう一つ有ります: 「derefによる型強制」です。 これは、 Deref<Target=T> を実装している型 U があるときに、 &U が自動的に &T に型強制されるというルールです。 例えば:

fn main() { fn foo(s: &str) { // // borrow a string for a second // 一瞬だけ文字列を借用します } // // String implements Deref<Target=str> // String は Deref<Target=str> を実装しています let owned = "Hello".to_string(); // // therefore, this works: // なので、以下のコードはきちんと動作します: foo(&owned); }
fn foo(s: &str) {
    // 一瞬だけ文字列を借用します
}

// String は Deref<Target=str> を実装しています
let owned = "Hello".to_string();

// なので、以下のコードはきちんと動作します:
foo(&owned);

値の前にアンパサンド(&)をつけることによってその値への参照を取得することができます。 なので、 ownedString であり、 &owned&String であり、 そして、 StringDeref<Target=str> を実装しているために、 &Stringfoo() が要求している &str に型強制されます。

以上です! このルールはRustが自動的に変換を行う唯一の箇所の一つです。 これによって、多くの柔軟性が手にはいります。 例えば Rc<T>Deref<Target=T> を実装しているため、以下のコードは正しく動作します:

fn main() { use std::rc::Rc; fn foo(s: &str) { // // borrow a string for a second // 文字列を一瞬だけ借用します } // // String implements Deref<Target=str> // String は Deref<Target=str>を実装しています let owned = "Hello".to_string(); let counted = Rc::new(owned); // // therefore, this works: // ゆえに、以下のコードは正しく動作します: foo(&counted); }
use std::rc::Rc;

fn foo(s: &str) {
      // 文字列を一瞬だけ借用します
}

// String は Deref<Target=str>を実装しています
let owned = "Hello".to_string();
let counted = Rc::new(owned);

// ゆえに、以下のコードは正しく動作します:
foo(&counted);

先ほどのコードとの変化は StringRc<T> でラッピングした点ですが、 依然 Rc<String>String が必要なところに渡すことができます。 foo のシグネチャは変化していませんが、どちらの型についても正しく動作します。 この例は2つの変換を含んでいます: Rc<String>String に変換され、次に String&str に変換されます。 Rustはこのような変換を型がマッチするまで必要なだけ繰り返します。

標準ライブラリに頻繁に見られるその他の実装は例えば以下の様なものが有ります:

fn main() { fn foo(s: &[i32]) { // // borrow a slice for a second // スライスを一瞬だけ借用します } // // Vec<T> implements Deref<Target=[T]> // Vec<T> は Deref<Target=[T]> を実装しています let owned = vec![1, 2, 3]; foo(&owned); }
fn foo(s: &[i32]) {
     // スライスを一瞬だけ借用します
}

// Vec<T> は Deref<Target=[T]> を実装しています
let owned = vec![1, 2, 3];

foo(&owned);

ベクタはスライスに Deref することができます。

Derefとメソッド呼び出し

Deref はメソッド呼び出し時にも自動的に呼びだされます。 例えば以下の様なコードを見てみましょう:

fn main() { struct Foo; impl Foo { fn foo(&self) { println!("Foo"); } } let f = &&Foo; f.foo(); }
struct Foo;

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

let f = &&Foo;

f.foo();

f&&Foo であり、 foo&self を引数に取るにも関わらずこのコードは動作します。 これは、以下が全て等価なことによります:

fn main() { f.foo(); (&f).foo(); (&&f).foo(); (&&&&&&&&f).foo(); }
f.foo();
(&f).foo();
(&&f).foo();
(&&&&&&&&f).foo();

&&&&&&&&&&&&&&&&Foo 型の値は Foo で定義されているメソッドを呼び出すことができます。 これは、コンパイラが自動的に必要なだけ * 演算子を補うことによります。 そして * が補われることによって Deref が利用される事になります。