web-dev-qa-db-ja.com

Rust特性オブジェクトのアップキャストをサポートしないのはなぜですか?

このコードを考えると:

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ではないのですか?

そのようなデザインの根拠を知っている人はいますか?

44
kFYatek

実は理由はわかったと思います。私はそれを望む任意のトレイトにアップキャストサポートを追加するエレガントな方法を見つけました、そしてその方法でプログラマーはトレイトに追加の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タイプでなければならないという要件が追加されます)が、これは一般的な方法です考え。これにより、すべての派生型のボイラープレートなしで、すべての派生型の安全でシンプルな(暗黙的ではない)アップキャストが可能になります。

45
kFYatek

2017年6月現在、この「サブ特性強制」(または「スーパー特性強制」)のステータスは次のとおりです。

  • 受け入れられたRFC #0401 は、これを強制の一部として言及しています。したがって、この変換は暗黙的に行われる必要があります。

    coerce_inner(T)= Uここで、TUのサブ特性です。

  • ただし、これはまだ実装されていません。対応する問題があります #186

重複する問題もあります #5665 。そこでのコメントは、これが実装されない原因を説明しています。

そこには---(@ typelist 彼らは準備したと言います ドラフトRFC はよく整理されているように見えますが、その後消えたように見えます(2016年11月)。

22
Masaki Hara

Rustを使い始めたとき、私は同じ壁に遭遇しました。さて、特性について考えるとき、クラスについて考えるときとは異なるイメージを心に抱いています。

trait X: Y {}は、構造体Xに特性Sを実装するときに、Yに特性Sを実装することも必要とすることを意味します。

もちろん、これは&X&Yでもあることを認識しているため、適切な関数を提供することを意味します。最初にYのvtableへのポインターをトラバースする必要がある場合は、実行時の労力(ポインターの逆参照の増加)が必要になります。

繰り返しになりますが、現在のデザインと他のvtableへの追加のポインターはおそらくそれほど害はなく、簡単にキャストを実装できます。では、両方が必要なのでしょうか?これは internals.Rust-lang.org で議論されるものです

17
oli_obk