問題の背景
Rustでは、型が他の値(例えば、BoxやRc)をカプセル化することが多く、開発者はC++のポインタのようにネストされた値に透明にアクセスしたいと考えました。Derefトレイト(およびDerefMut)が追加され、メソッドや演算子を介して便利にアクセスするための自動逆参照ロジックが導入されました。
問題点
ポインタやスマートポインタがいつどのように自動的に展開されるかについての混乱は、コンパイルエラーやメソッドの予期しない動作を引き起こし、時には余分なコピーや借用のせいで非最適なコードを生じることがあります。
解決策
Derefトレイトは自分の逆参照ルールを定義することを可能にします(例えば、Box<T>はDerefを実装することでTのように振る舞います)。自動逆参照のシステムは、メソッドや演算子にアクセスするときにDeref型への参照を「展開」しようとします。これにより、以下のように記述できます:
use std::rc::Rc; fn print_len(s: &str) { println!("{}", s.len()); } let s: Rc<String> = Rc::new("hello".into()); print_len(&s); // auto-deref: &Rc<String> -> &String -> &str
主な特徴:
*、および参照型のパターンマッチング時に機能します。デレフは開発者が「期待する」時に常に自動で行われますか?
いいえ:デレフコーションは、メソッド呼び出し時またはDeref Targetを通じてシグネチャで指定された別の型への参照が必要な場合にのみ発生します。これはすべての式では機能しません。例えば、パターンマッチングでは機能しません。
デレフコーションは借用や所有のルールを破る可能性がありますか?
いいえ。デレフコーションは借用のルールを完全に遵守します。Derefを介して二つのmutable参照を得ることは不可能であり、Rustの所有権の安全性を損なうことはありません。
&Tから&Uへの自動逆参照は可能ですか?T: Deref<Target=U>であり、両方の型が明示的に関連付けられていない場合は?
はい、&Uを期待する関数を呼び出す場合、&Tを渡すと、TがDeref<Target=U>を実装していると自動的に変換されます。
コード例:
struct Wrapper(String); impl std::ops::Deref for Wrapper { type Target = String; fn deref(&self) -> &Self::Target { &self.0 } } fn takes_str(s: &str) {} let w = Wrapper("mytext".into()); takes_str(&w); // auto-deref: &Wrapper -> &String -> &str
開発者が独自のスマートポインタを実装しましたが、Derefの実装を追加せず、自分の型が通常の値として振る舞うことを期待しました。
利点:
型がコンパイルされ、ネストされた値に明示的にアクセスできる。
欠点:
メソッド呼び出し時に自動逆参照が機能せず、標準ライブラリやサードパーティのライブラリを使用する際の不便さが生じます。
独自のラッパー型がDerefおよびDerefMutをBoxと同様に実装しており、すべての標準関数とのシームレスな統合が可能です。
利点:
便利さ、言語のほとんどの機能の透明な動作、コードの整然さと柔軟な統合。
欠点:
ターゲット型が明確でない場合、Derefインターフェースを複雑化するリスクがあります。