Zero-SizedなオブジェクトをVecに入れてみる

Zero-Sizedなオブジェクトは、メンバを持たない構造体から作ることができます。

struct Blank;
// struct Blank {} と同じ

fn main() {
  println!("{}", std::mem::size_of::<Blank>()); // => 0
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=502ca273485513e57f061fa22755f767

ちなみに、メンバを持たないenumもサイズは0です

enum BlankEnum;

fn main() {
  println!("{}", std::mem::size_of::<BlankEnum>()); // => 0
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=6a62ebde80162cdbad058521f5425e7e

(が、メンバがないのでenumからZero-Sizedのオブジェクトを作り出すことはできません)

ちなみに②、
上記の通りenumのサイズはメンバの"数"によってきまり、
メンバが1個なら0 byte,
1~256個なら1,
257~多分65536個なら2,
.
.
.
となります(なるはずです)

enum A {
  Member1, Member2, ... Member256
}

enum B {
  Member1, Member2, ... Member256, Member257
}

fn main() {
  println!("{}", std::mem::size_of::<A>()); // => 1
  println!("{}", std::mem::size_of::<B>()); // => 2
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=7d68970ccbe150fed081606e2b0a9e07

(一応断っておくと、すべてがデータを持たないenumの場合の話です)

さて話は戻って、
Vecは[T]の先頭へのポインタ*const T(≠ &[T])と キャパシティ(usize)と 長さ(usize)

からなる構造体なので、
ここにZeroSizedなオブジェクトを入れたらどうなるのだろうと、やってみました

ちなみに③、
0サイズのオブジェクトでもアロケーションはされている(と言ってよいのだろうか)ようです

struct Blank;

fn main() {
    let obj_in_vain = Blank{};
    println!("{:?}", &obj_in_vain as *const Blank); // => <なんらかのアドレスが印字される>
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=d94bddc4d0e323f764b3e41f42612e10

さて、今度こそVecにZeroSizedなオブジェクトをいれていきます

struct Blank;

fn main() {
    let mut blank_vec = Vec::<Blank>::new();
    blank_vec.push(Blank{});
    blank_vec.push(Blank{});
    blank_vec.push(Blank{});
    println!("length: {}", blank_vec.len()); // length: 3
    println!("capacity: {}", blank_vec.capacity()); // capacity: 18446744073709551615
    println!("ptr: {:?}", blank_vec.as_ptr()); // ptr: 0x1
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=2edc3590213956f140927f7cab338755

長さは普通です。 キャパシティは18446744073709551615と std::usize::MAX_VALUEが返ってますね。
これはここの実装によりそうです。
そもそもサイズが0のものは理論上無限に詰め込んでよいはずなので、usizeの最大値が返るのはなんとなくわかる話です。
そしてポインタですが、 0x1になっています。

空のオブジェクトはアロケーションされても、空のオブジェクトのVecはどこも指さないんですね。

勉強になりました。

ちなみに④、
なぜ 0x0でなく 0x1なのかというと、
参照はNonZeroだからのような気がします。
nullポインタ最適化ってやつのためでしょうか。

struct Blank;

fn main() {
    println!("{}", std::mem::size_of::<usize>());
    println!("{}", std::mem::size_of::<Option<usize>>());
    
    println!("{}", std::mem::size_of::<&usize>());
    println!("{}", std::mem::size_of::<Option<&usize>>()); // なんと増えない
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=d33b62b9d79419061d65796878861116

Rustおもしろいですね。