34322


スタックに "Trait を実装したオブジェクト" を格納したいです。  
Rust では `Vec` をスタックとして使うのが通例だということで、以下のコードを書いたのですが、Trait のサイズがコンパイル時に定まらないので
`Vec` には格納できません。 (コンパイルエラー)  
代替案、または解決策はあるでしょうか。

[code]

    trait Trait<T>{}
    
    struct Struct<T> {
        a: Vec<Trait<T>>
    }
    
    fn main() {}
    
[/code]

コンパイルエラーのメッセージは以下です。

[code]

    error[E0277]: the trait bound `Trait<B> + 'static: std::marker::Sized` is not satisfied.
      = note: `Trait<B> + 'static` does not have a constant size known at compile-time
      = note: required by `std::vec::Vec`
    
[/code]


CC BY-SA 3.0
2017-04-27T08:16:02.790
34317
2017-04-28T14:14:04.237
2017-04-28T14:14:04.237
18735
18735
question
rust
Rust: Vec にトレイトを実装したオブジェクトを格納したい


よくある問題ですね。これには2つ解決策があります。ジェネリクスとトレイトオブジェクトです。

# ジェネリクス

`Struct`に型引数を追加して、実装している型も受け取るスタイルです。今回ちょっと難しいのは`Trait`自身が引数を取る（のとRustが高階多相をサポートしていない）ので[`PhantomData`](https://doc.rust-
lang.org/core/marker/struct.PhantomData.html)というダミー用のフィールドを足してunused type
parameterエラーを防ぐ必要があります。

[code]

    use std::marker::PhantomData;
    
    trait Trait<T> {}
    
    struct Struct<Impl, T>
        where Impl: Trait<T>
    {
        a: Vec<Impl>,
        phantom: PhantomData<T>,
    }
    
    fn main() {}
    
[/code]

# トレイトオブジェクト

もう1つは[トレイトオブジェクト](https://rust-lang-ja.github.io/the-rust-programming-
language-ja/1.6/book/trait-objects.html)で、雑に言うと`Box`で包んであげれば解決できます。

[code]

    trait Trait<T> {}
    
    struct Struct<T> {
        a: Vec<Box<Trait<T>>>,
    }
    
    fn main() {}
    
[/code]

正確にいうと`&`や`&mut`、`Rc`などでも大丈夫ですが、ポインタ型でトレイトを参照してあげることでサイズ不定のエラーを防ぐことができます。

# 比較

見た目の話ではジェネリクスの方が少し煩雑ですが、静的に解決されるので高速に動作します。  
一方トレイトオブジェクトはポインタを挟むので少し遅くなります（もう少しいうとvtableというのを使うので単なるポインタの参照より遅いです）

速度以外の点でいうと、トレイトオブジェクトは複数種類の実装を交ぜることができます。

[code]

    trait Trait<T> {}
    
    struct Struct<T> {
        a: Vec<Box<Trait<T>>>,
    }
    
    impl<T> Trait<T> for () {}
    impl<T> Trait<T> for bool {}
    
    
    fn main() {
        let mut s: Struct<()> = Struct { a: vec![] };
        s.a.push(Box::new(()));
        s.a.push(Box::new(true));
    }
    
[/code]

私は多くの場合ジェネリクスを使いますが、ジェネリクスを使うと型が煩雑になりすぎる場合やこのように複数の実装を交ぜたいときにはトレイトオブジェクトを使います。


CC BY-SA 3.0
2017-04-27T09:55:37.207
34322
2017-04-27T09:55:37.207
22816
34317
answer


おっしゃる通り、Traitのサイズ（厳密には、Traitを実装したあらゆる具象型のサイズ）はコンパイル時に一意に定まりません。以下のコードはコンパイルエラーになります。

[code]

    use std::mem;
    
    trait Trait<T> {}
    
    fn main() {
        println!("{}", mem::size_of::<Trait<i32>>());
        // 　→ エラー：the trait `std::marker::Sized` is not
        // 　　 　　　　implemented for `Trait<i32>`
    }
    
[/code]

一方で、Traitを指すポインタのサイズならコンパイル時に定まります。Boxという所有権を持つポインタがありますので、Traitをそれに入れます。これをボックス化と呼びます。

[code]

    fn main() {
        println!("{}", mem::size_of::<Box<Trait<i32>>>());
        // 　→ 16
    }
    
[/code]

この方法を使うと、質問の例は以下のようになります。

[code]

    trait Trait<T> {
        fn get_item(&self) -> &T;
    }
    
    struct Struct<T> {
        a: Vec<Box<Trait<T>>>,
    }
    
    struct Green<T>(T);
    
    impl<T> Trait<T> for Green<T> {
        fn get_item(&self) -> &T {
            &self.0
        }
    }
    
    struct Blue<T>(T);
    
    impl<T> Trait<T> for Blue<T> {
        fn get_item(&self) -> &T {
            &self.0
        }
    }
    
    fn main() {
        let mut s = Struct { a: Vec::new() };
        s.a.push(Box::new(Green(0)));
        s.a.push(Box::new(Blue(1)));
    
        for elem in &s.a {
            println!("{}", elem.get_item());
        }
    }    
    
[/code]

なお、ボックス化したトレイトを「トレイトオブジェクト」と呼びます。トレイトオブジェクトに対するメソッド呼び出しでは、動的ディスパッチという手法が取られるので、もう一つの手法である静的ディスパッチに対して性能面でやや不利になります。（ちなみに、RustとC++を除くほとんどのオブジェクト指向言語では、動的ディスパッチのみをサポートしています。つまり最初からボックス化されています）

トレイトオブジェクトに関して、詳しくはこちらを参照してください。  
<https://rust-lang-ja.github.io/the-rust-programming-language-
ja/1.6/book/trait-objects.html>


CC BY-SA 3.0
2017-04-27T09:57:59.637
34323
2017-04-27T10:15:39.747
2017-04-27T10:15:39.747
14101
14101
34317
answer