サイズ不定型

ほとんどの型はコンパイル時に知れる、バイト数で測った、サイズがあります。 例えば、 i32 型は、32ビット(4バイト)というサイズです。 しかしながら、表現のためには便利であってもサイズが定まっていない型が存在します。 そのような型を 「サイズ不定」又は「動的サイズ」型と呼びます。 一例を上げると [T] 型は 一定のサイズのT のシーケンスを意味していますが、その要素数については規定されていないため、サイズは不定となります。

Rustはいくつかのそのような型を扱うことができますが、それらには以下の様な3つの制約が存在します:

  1. サイズ不定型はポインタを通してのみ操作することができます、たとえば、 &[T] は大丈夫ですが、 [T] はそうではありません。
  2. 変数や引数は動的なサイズを持つことはできません。
  3. struct の最後のフィールドのみ、動的なサイズを持つことが許されます、その他のフィールドはサイズが不定であってはなりません。 また、Enumのバリアントはデータとして動的なサイズの型を持つ事はできません。

なぜこんなにややこしいのでしょうか? これは、[T] はポインタを通してのみ操作可能であるため、 もし言語がサイズ不定型をサポートしていなかった場合、以下のようなコードを書くことは不可能となります:

fn main() { impl Foo for str { }
impl Foo for str {

また、以下の様なコードも:

fn main() { impl<T> Foo for [T] { }
impl<T> Foo for [T] {

このように書く代わりに、以下のように書く必要があることになるでしょう:

fn main() { impl Foo for &str { }
impl Foo for &str {

このように書いたとすると、このコードは 参照 に対してのみ動作するようになり、他のポインタ型に対しては動作しないことになります。 imp for str のように書くことで、すべてのポインタ、ユーザーの定義した独自のスマートポインタ(いくつかの点についてバグがあるので、それを先ずは直さなくてはなりませんが)もこの impl を利用可能になります。

?Sized

もし動的サイズ型を引数に取れるような関数を定義したい場合、特別な境界 ?Sized を利用できます:

fn main() { struct Foo<T: ?Sized> { f: T, } }
struct Foo<T: ?Sized> {
    f: T,
}

? は 「Tは Sized かもしれない」と読みます、これは ? が特別な境界: より小さいカインドとマッチするのではなく、より大きいカインドとマッチする ということを意味しています。 これは、すべての T は暗黙的に T : Sized という制限がかけられていて、 ? はその制限を解除するというようなものです。