このコードを考えると:
trait Base {
fn a(&self);
fn b(&self);
fn c(&self);
fn d(&self);
}
trait Derived : Base {
fn e(&self);
fn f(&self);
fn g(&self);
}
struct S;
impl Derived for S {
fn e(&self) {}
fn f(&self) {}
fn g(&self) {}
}
impl Base for S {
fn a(&self) {}
fn b(&self) {}
fn c(&self) {}
fn d(&self) {}
}
残念ながら、&Derived
を&Base
にキャストできません。
fn example(v: &Derived) {
v as &Base;
}
error[E0605]: non-primitive cast: `&Derived` as `&Base`
--> src/main.rs:30:5
|
30 | v as &Base;
| ^^^^^^^^^^
|
= note: an `as` expression can only be used to convert between primitive types. Consider using the `From` trait
何故ですか? Derived
vtableは、何らかの方法でBase
メソッドを参照する必要があります。
LLVM IRを検査すると、次のことがわかります。
@vtable4 = internal unnamed_addr constant {
void (i8*)*,
i64,
i64,
void (%struct.S*)*,
void (%struct.S*)*,
void (%struct.S*)*,
void (%struct.S*)*
} {
void (i8*)* @_ZN2i813glue_drop.98717h857b3af62872ffacE,
i64 0,
i64 1,
void (%struct.S*)* @_ZN6S.Base1a20h57ba36716de00921jbaE,
void (%struct.S*)* @_ZN6S.Base1b20h3d50ba92e362d050pbaE,
void (%struct.S*)* @_ZN6S.Base1c20h794e6e72e0a45cc2vbaE,
void (%struct.S*)* @_ZN6S.Base1d20hda31e564669a8cdaBbaE
}
@vtable26 = internal unnamed_addr constant {
void (i8*)*,
i64,
i64,
void (%struct.S*)*,
void (%struct.S*)*,
void (%struct.S*)*,
void (%struct.S*)*,
void (%struct.S*)*,
void (%struct.S*)*,
void (%struct.S*)*
} {
void (i8*)* @_ZN2i813glue_drop.98717h857b3af62872ffacE,
i64 0,
i64 1,
void (%struct.S*)* @_ZN9S.Derived1e20h9992ddd0854253d1WaaE,
void (%struct.S*)* @_ZN9S.Derived1f20h849d0c78b0615f092aaE,
void (%struct.S*)* @_ZN9S.Derived1g20hae95d0f1a38ed23b8aaE,
void (%struct.S*)* @_ZN6S.Base1a20h57ba36716de00921jbaE,
void (%struct.S*)* @_ZN6S.Base1b20h3d50ba92e362d050pbaE,
void (%struct.S*)* @_ZN6S.Base1c20h794e6e72e0a45cc2vbaE,
void (%struct.S*)* @_ZN6S.Base1d20hda31e564669a8cdaBbaE
}
すべてのRust vtableには、デストラクタへのポインタ、サイズ、および配置が最初のフィールドに含まれており、サブトレイトvtableは、スーパートレイトメソッドを参照するときにそれらを複製せず、スーパートレイトvtableへの間接参照を使用しません。メソッドポインタのコピーはそのままで、他には何もありません。
その設計を考えると、これが機能しない理由を理解するのは簡単です。新しいvtableは実行時に構築する必要があり、スタックに常駐する可能性がありますが、これはエレガント(または最適)なソリューションではありません。
もちろん、明示的なアップキャストメソッドをインターフェイスに追加するなど、いくつかの回避策がありますが、適切に動作するには、かなりのボイラープレート(またはマクロフレンジー)が必要です。
さて、問題は-トレイトオブジェクトのアップキャストを可能にするような方法で実装されないのはなぜですか?同様に、サブトレイトのvtableにスーパートレイトのvtableへのポインターを追加します。今のところ、Rustの動的ディスパッチは Liskov置換原理 を満足していないようです。これはオブジェクト指向設計の非常に基本的な原理です。
もちろん、静的ディスパッチを使用することもできます。これは、Rustで使用するのに非常にエレガントですが、組み込みシステムのように、計算パフォーマンスよりも重要な場合があるコード膨張に簡単につながり、Rust開発者はそのようなサポートを主張します。言語の使用例。また、多くの場合、純粋にオブジェクト指向ではないモデルを正常に使用できます。これは、Rustの機能設計に推奨されているようです。それでも、Rustは便利なOOパターンの多くをサポートしています...では、なぜLSPではないのですか?
そのようなデザインの根拠を知っている人はいますか?
実は理由はわかったと思います。私はそれを望む任意のトレイトにアップキャストサポートを追加するエレガントな方法を見つけました、そしてその方法でプログラマーはトレイトに追加のvtableエントリを追加するかどうかを選択できます。これは次のようなトレードオフです。 C++の仮想メソッドと非仮想メソッド:優雅さとモデルの正確さ対パフォーマンス。
コードは次のように実装できます。
trait Base: AsBase {
// ...
}
trait AsBase {
fn as_base(&self) -> &Base;
}
impl<T: Base> AsBase for T {
fn as_base(&self) -> &Base {
self
}
}
&mut
ポインタまたはBox
をキャストするためのメソッドを追加できます(T
が'static
タイプでなければならないという要件が追加されます)が、これは一般的な方法です考え。これにより、すべての派生型のボイラープレートなしで、すべての派生型の安全でシンプルな(暗黙的ではない)アップキャストが可能になります。
2017年6月現在、この「サブ特性強制」(または「スーパー特性強制」)のステータスは次のとおりです。
coerce_inner(
T
)=U
ここで、T
はU
のサブ特性です。
重複する問題もあります #5665 。そこでのコメントは、これが実装されない原因を説明しています。
+ ----- + ------------------------------- + | 0- 7 |「接着剤のドロップ」機能へのポインター| + ----- + -------------------------- ----- + | 8-15 |データのサイズ| + ----- + ----------------------------- -+ | 16-23 |データの配置| + ----- + -------------------- ----------- + | 24 自己とスーパー特性の方法| + ----- + ----------- -------------------- +サブシーケンスとしてスーパー特性のvtableが含まれていません。少なくともvtableを調整する必要があります。
そこには---(@ typelist 彼らは準備したと言います ドラフトRFC はよく整理されているように見えますが、その後消えたように見えます(2016年11月)。
Rustを使い始めたとき、私は同じ壁に遭遇しました。さて、特性について考えるとき、クラスについて考えるときとは異なるイメージを心に抱いています。
trait X: Y {}
は、構造体X
に特性S
を実装するときに、Y
に特性S
を実装することも必要とすることを意味します。
もちろん、これは&X
が&Y
でもあることを認識しているため、適切な関数を提供することを意味します。最初にY
のvtableへのポインターをトラバースする必要がある場合は、実行時の労力(ポインターの逆参照の増加)が必要になります。
繰り返しになりますが、現在のデザインと他のvtableへの追加のポインターはおそらくそれほど害はなく、簡単にキャストを実装できます。では、両方が必要なのでしょうか?これは internals.Rust-lang.org で議論されるものです