I hit this writing a chess engine: my board’s mailbox is [64]?Piece, and it’s 128 bytes instead of 64, because ?Piece is 2 bytes — even though Piece only uses 12 of the 256 possible tag values.
An exhaustive enum(u8) with fewer than 256 variants has unused tag values, i.e. a niche that could encode null. But Zig doesn’t use it:
const std = @import("std");
const Piece = enum(u8) {
wp, wn, wb, wr, wq, wk,
bp, bn, bb, br, bq, bk,
}; // 12 variants → tags 12..=255 are invalid
pub fn main() void {
std.debug.print("@sizeOf(Piece) = {d}\n", .{@sizeOf(Piece)}); // 1
std.debug.print("@sizeOf(?Piece) = {d}\n", .{@sizeOf(?Piece)}); // 2
std.debug.print("@sizeOf(?*Piece) = {d}\n", .{@sizeOf(?*Piece)}); // 8
}
@sizeOf(Piece) = 1
@sizeOf(?Piece) = 2
@sizeOf(?*Piece) = 8
What makes me think this is “could be better” rather than fundamental: ?*Piece is 8 bytes — the same as *Piece — because optional pointers already get a niche (null = address 0). So the optional-niche machinery exists; it just isn’t applied to the spare discriminant values of an enum, where ?Piece instead pays a full extra flag byte (plus padding for larger payloads).
For reference, the analogous type in Rust folds None into an unused tag (Option<Piece> is 1 byte, and it even nests — Option<Option<Piece>> is still 1), so the optimization is at least known to be feasible.
#[repr(u8)]
enum Piece { Wp, Wn, Wb, Wr, Wq, Wk, Bp, Bn, Bb, Br, Bq, Bk }
fn main() {
println!("{}", std::mem::size_of::<Piece>()); // 1
println!("{}", std::mem::size_of::<Option<Piece>>()); // 1 <- niche
println!("{}", std::mem::size_of::<Option<Option<Piece>>>()); // 1 <- nested niches!
}
My questions:
- Is this an intentional design decision (layout stability, keeping
?Tpredictable, comptime simplicity…), or just not implemented yet? - Is there an existing issue or proposal tracking it? I’d rather read the rationale than assume it’s an oversight.
- Is there an idiomatic way to opt in today? For now I hand-rolled a sentinel —
const Slot = enum(u8) { empty = 12, _ };with an accessor that returns?Piece— which gets the mailbox back to 64 bytes, but it’d be nice if?Piecejust did this.
Thanks!