sized 型
Rust のほとんどの型は、値のメモリ上におけるサイズが既知であり、このような型を sized型 と呼ぶ。
u64
は 8 バイトであり、(f32, f32, f32)
型のタプルは 12 バイトであり、sized型 である。
さらに、列挙型や Vec<T>
値でさえも sized型 である。
列挙型は、常に最大のヴァリアントを保持できる空間を要する sized型 であり、Vec<T>
値そのものはバッファへのポインタと容量と長さを保持する sized型 である(ヒープ上に所有されるバッファ自体は可変)。
このように Rust のほとんどの型は sized 型であるが、unsized な型もいくつか存在する。
文字列スライス型 str
や、[T]
のような配列スライス型(&
が付いていないことに注意)は、さまざまなサイズの値の集合を指すため unsized 型であり、トレイトオブジェクトとして参照される dyn
型も unsized 型である。
最後のフィールドが Sized
でない struct
も unsized 型である(最後のフィールドだけを unsized 型とすることができる)。
Rustでは、unsized 型の値は、変数に格納したり、引数として渡すことはできない。そのため、&str
や Box<dyn Write>
のようにポインタを介して扱わなければならない(ポインタはサイズが決まっている)。
std::marker::Sized トレイト
sized 型 は、暗黙裡に std::marker::Sized
トレイトを実装する(Rust コンパイラが、適用できるすべての型に自動的に実装し、自身で実装することはできない)。
std::marker::Sized
トレイト自体は空実装であり、マーカートレイトとなっている。
pub trait Sized { // Empty. }
ほとんどの型は sized 型であるため、型変数は sized 型 が暗黙のデフォルトとなっている。
struct S<T> { ... }
と書けば、struct S<T: Sized> { ... }
と解釈される。
トレイト境界として Sized
の制約を課したくない場合は、Dynamically Sized Types として ?Sized
を明示的に指定する必要がある。
struct S<T: ?Sized> { ... }
?Sized
は、Sized かもしれないし、そうでないかもしれないということを意味する。
以下のように定義すれば、
struct S<T: ?Sized> { b: Box<T> }
S<i32>
のように書くこともできるし、S<str>
や S<dyn Write>
のように書くこともできる。
S<i32>
とした場合は sized型 となり、Box は通常のポインタ(8byte)となるが、S<str>
や S<dyn Write>
とした場合はファットポインタ(16byte)となる。つまり、ポインタアドレスに加え、可変領域の長さの情報が付与されることになる。