In C++, you have `std::optional<T>`. That's a type that either contains a `T` or contains nothing.
In C++, sizeof(optional<T>) > sizeof(T) because the discriminat has to be stored somewhere.
This is true even if, e.g., you do something like `optional<T&>`. You know that T& is a non-null pointer, and you only have two variants, and one of the variants has no state, so you technically can encode this as 0x0 is the "no reference" variant, and the != 0x0 is the reference variant, and have `optional<T&>` have the same size as `T&`.
Rust does these layout optimizations of compressing the discriminant into gaps in the values of discriminated unions automatically.
So:
enum Option<T> {
Some(T),
None
}
for `Option<T&>` has the same size as `T&` in Rust, as opposed to C++.
In C, an example would be:
struct DU {
enum { A, B } discriminant;
union {
bool A;
bool B;
}
};
You could encode that into 3 bits (i.e. have sizeof(DU) == 1), but instead you'll have at least sizeof(DU) == 2, because you need one byte for the discriminant, and one byte for the payload.
It is very easy to create values with gaps in Rust, but C doesn't really support doing this.
Another optimization are alignment optimizations. In C, if you write:
struct S {
uint8_t a;
uint32_t b;
uint8_t c;
};
that ends up being 12 bytes long. In Rust, by default, that gets reordered as uint32_t, uint8_t, uint8_t, so it only ends up being 8 bytes.
If you want that instead to be laid out like in C, you can write:
#[repr(C)]
struct S {
a: u8,
b: u32,
c: u8
}
and then you get the same 12 bytes as in C. There are many supported `repr(...)` options supported for algebraic data types, e.g., you can use repr(u32) for the Rust enum above to store the discriminant in a u32, and get the same layout as DU in C.