注意: 最新版のドキュメントをご覧ください。この第1版ドキュメントは古くなっており、最新情報が反映されていません。リンク先のドキュメントが現在の Rust の最新のドキュメントです。
標準ライブラリは特別なトレイト 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); }
このように、 Deref
はカスタマイズしたポインタ型を定義するのに便利です。
一方で、Deref
に関連する機能がもう一つ有ります: 「derefによる型強制」です。
これは、 Deref<Target=T>
を実装している型 U
があるときに、
&U
が自動的に &T
に型強制されるというルールです。
例えば:
fn foo(s: &str) { // 一瞬だけ文字列を借用します } // String は Deref<Target=str> を実装しています let owned = "Hello".to_string(); // なので、以下のコードはきちんと動作します: foo(&owned);
値の前にアンパサンド(&)をつけることによってその値への参照を取得することができます。
なので、 owned
は String
であり、 &owned
は &String
であり、
そして、 String
が Deref<Target=str>
を実装しているために、
&String
は foo()
が要求している &str
に型強制されます。
以上です! このルールはRustが自動的に変換を行う数少ない箇所の一つです。
これによって、多くの柔軟性が手にはいります。
例えば Rc<T>
は Deref<Target=T>
を実装しているため、以下のコードは正しく動作します:
use std::rc::Rc; fn foo(s: &str) { // 文字列を一瞬だけ借用します } // String は Deref<Target=str>を実装しています let owned = "Hello".to_string(); let counted = Rc::new(owned); // ゆえに、以下のコードは正しく動作します: foo(&counted);
先ほどのコードとの変化は String
を Rc<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
はメソッド呼び出し時にも自動的に呼びだされます。
例えば以下の様なコードを見てみましょう:
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
が利用される事になります。