北海道苫小牧市出身の初老PGが書くブログ

永遠のプログラマを夢見る、苫小牧市出身のおじさんのちらしの裏

&Tがそのまま&dyn Traitとして使えるわけではない

トレイトオブジェクトを返す次の関数 fコンパイルできる。

trait X {}

fn f(x: &dyn X) -> &dyn X {
    x
}

ところが、次のように書くとコンパイルできない。

fn f<T: X + ?Sized>(x: &T) -> &dyn X {
    x
}
   Compiling playground v0.0.1 (/playground)
error[E0277]: the size for values of type `T` cannot be known at compilation time
 --> src/lib.rs:4:5
  |
3 | fn f<T: X + ?Sized>(x: &T) -> &dyn X {
  |      - this type parameter needs to be `std::marker::Sized`
4 |     x
  |     ^ doesn't have a size known at compile-time
  |
  = help: the trait `std::marker::Sized` is not implemented for `T`
  = note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
  = note: required for the cast to the object type `dyn X`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0277`.
error: could not compile `playground`.

To learn more, run the command again with --verbose.

T はトレイト X を実装しているのだから、何もしなくても &dyn X なのではと思われるかもしれないが、この書き方をした f では x が直接返されるわけではなく、トレイトオブジェクトに変換される。トレイトオブジェクトについては Trait object types - The Rust Reference に記述がある。 Exploring Rust fat pointers でもトレイトオブジェクトの構造 (fat pointers) についてわかりやすく説明されている。

Each instance of a pointer to a trait object includes: a pointer to an instance of a type T that implements SomeTrait a virtual method table, often just called a vtable, which contains, for each method of SomeTrait and its supertraits that T implements, a pointer to T's implementation (i.e. a function pointer).

この変換時になぜ Sized が必要なのかは、以下の issue で説明されている。 [u8] のサイズの格納位置が &&dyn の場合で違うため、単純にポインタを流用してトレイトオブジェクトを作ることができないようだ。なるほど。

github.com

A legitimate example of why the Sized bound is needed is for e.g. Self = [u8]. You cannot cast &[u8] to &dyn Trait because there would be nowhere to store the length of the slice (&[u8] stores it in the same place where &dyn Trait puts its vtable).

ただ、この issue がなぜクローズされていないかはよくわからず。将来挙動が変わる見込みがあるんだろうか?