Deref ポリモーフィズム
説明
構造体間の継承をエミュレートするために Deref トレイトを誤用し、メソッドを再利用します。
例
Java などのオブジェクト指向言語で一般的な以下のパターンをエミュレートしたい場合があります:
class Foo {
void m() { ... }
}
class Bar extends Foo {}
public static void main(String[] args) {
Bar b = new Bar();
b.m();
}
これを実現するために、deref ポリモーフィズムアンチパターンを使用できます:
use std::ops::Deref; struct Foo {} impl Foo { fn m(&self) { //.. } } struct Bar { f: Foo, } impl Deref for Bar { type Target = Foo; fn deref(&self) -> &Foo { &self.f } } fn main() { let b = Bar { f: Foo {} }; b.m(); }
Rust には構造体の継承がありません。代わりにコンポジションを使用し、Bar に Foo のインスタンスを含めます(フィールドが値であるため、インライン格納されます。そのため、フィールドがある場合、メモリレイアウトは Java 版と同じになります(おそらく。確実にしたい場合は #[repr(C)] を使用すべきです))。
メソッド呼び出しを機能させるために、Foo をターゲットとして Bar に Deref を実装します(埋め込まれた Foo フィールドを返します)。これは、Bar を参照外し(例えば * を使用)すると Foo が得られることを意味します。これはかなり奇妙です。通常、参照外しは T への参照から T を与えますが、ここでは2つの無関係な型があります。しかし、ドット演算子は暗黙的な参照外しを行うため、メソッド呼び出しは Bar だけでなく Foo のメソッドも検索することを意味します。
利点
少しのボイラープレートを節約できます。例えば:
impl Bar {
fn m(&self) {
self.f.m()
}
}
欠点
最も重要なのは、これが驚くべきイディオムであることです - このコードを読む将来のプログラマーは、これが起こることを期待しません。なぜなら、意図された通り(およびドキュメント化された通りなど)に Deref トレイトを使用するのではなく、誤用しているからです。また、ここでのメカニズムが完全に暗黙的であるためでもあります。
このパターンは、Java や C++ の継承のような Foo と Bar の間にサブタイピングを導入しません。さらに、Foo によって実装されたトレイトは自動的に Bar に実装されないため、このパターンは境界チェックとジェネリックプログラミングに悪影響を与えます。
このパターンを使用すると、self に関してほとんどのオブジェクト指向言語とは微妙に異なるセマンティクスが与えられます。通常はサブクラスへの参照のままですが、このパターンではメソッドが定義されている「クラス」になります。
最後に、このパターンは単一継承のみをサポートし、インターフェース、クラスベースのプライバシー、その他の継承関連の機能の概念がありません。そのため、Java の継承などに慣れたプログラマーにとって微妙に驚くべき体験を与えます。
議論
唯一の良い代替案はありません。正確な状況に応じて、トレイトを使用して再実装するか、Foo にディスパッチするファサードメソッドを手動で記述する方が良い場合があります。Rust にこれに類似した継承メカニズムを追加する予定ですが、安定版 Rust に到達するまでにはかなりの時間がかかる可能性があります。詳細については、これらのブログ投稿とこのRFC issueを参照してください。
Deref トレイトは、カスタムポインタ型の実装用に設計されています。意図は、T へのポインタを T に変換することであり、異なる型間の変換ではありません。これがトレイト定義によって強制されていない(おそらくできない)のは残念なことです。
Rust は、明示的メカニズムと暗黙的メカニズムの間で慎重なバランスを取ろうとしており、型間の明示的な変換を好みます。ドット演算子での自動参照外しは、エルゴノミクスが暗黙的メカニズムを強く支持するケースですが、意図は、これが間接参照の度合いに限定され、任意の型間の変換ではないことです。
関連項目
- コレクションはスマートポインタのイディオム
- より少ないボイラープレートのための委譲クレート: delegate または ambassador
Derefトレイトのドキュメント