type A = Result<() ()>;
type B = Result<() bool>;
type C = Result<bool, ()>;
type D = Result<bool, bool>;
A, B and C take up one byte because the compiler uses the invalid bit patterns (anything except 0x00 and 0x01) to represent the 1-bit information Result needs.
D however takes up two bytes, even though it has almost the same amount of available patterns. The compiler could use the following representations:
0x00: Ok(false)
0x01: Ok(true)
0x10: Err(false)
0x11: Err(true)
I know that rust doesn't use individual bits to optimize away that extra byte, but all patterns in the range 0x02..=0xff are unused/invalid for both the Ok and Err types.
I assume this is because the compiler only attempts this optimization if one of the types is ZST?
Is there another reason why this optimization isn't done?
For context: I've noticed this while looking through the Generic integers Pre-RFC:
Using a smaller number of bits for a value also has the benefit of niche enum optimisations. For example, u<7> represents a single ASCII character, and Option<u<7>> can be stored in a single byte. Additionally, Result<u<7>, E> also takes one byte if E is zero-sized.
This works because we know that values in the range 0b0_0000000..=0b0_1111111 are valid u<7>, but the values in the range 0b1_0000000..=0b1_1111111 are not and can represent the zero-sized variants None and Err(E).
In the case of D, it needs to be possible to get a shared reference to the bool inside the Result using only a shared reference to the Result. That means, for instance, that the representations for both Ok(false) and Err(false) need to contain a 0x00 byte.
Also look up "move-only fields" for discussion of the feature that would let the user give up some things in order to give the compiler more freedom to do layout optimizations like this.
Since byte_representation borrows from possibility, and since it only has a single byte, if Result<bool, bool> was optimized to also be a single byte they would have necessarily take up the same space. But according to the assertion - which must hold for booleans, according to Rust's rules - that byte can only have two options - 0 and 1. It is impossible to represent the four options of Result<bool, bool> with that single byte under these constraints.
That reminds me quite a bit of the "pinned places" suggestion (almost the opposite), which takes away the ability to move the type (after some point in time), thus allowing self-references. If I understood it correctly a "move-only" or "not-referenceable" type/place/variable/value instead takes away the ability to make references to it (or its base fields), while keeping the ability to move the value.
Though that's probably not a good suggestion for this, as that would prevent layout optimizations (which as far as I can tell stay the same between pinned and not pinned).
And it adds a bunch of complexity for little benefit.
Anyways, thanks for all the responses (on a topic that probably should've been in the user forum).