Why is Option<(u64, u32)> larger than Option<(u64, u32, NonZeroU32)>?

On 64-bit architecture I would expect both to be 16 bytes since they both leave a niche for the Option bit, but Option<(u64, u32)> counter-intuitively turns out to be larger at 24 bytes wide, why is this?
Is it a good idea to add a dummy NonZeroU32 field to make the Option smaller or is there some downside to this (assuming again a 64-bit architecture)?

Padding is allowed to have any value, so there is no niche in (u64, u32).

And the size of (u64, 32) must be 16 rather than 12 because the alignment is 8 and the size must be a multiple of the alignment.

3 Likes

thank you, I see.
Yes, the reasons for 16 bytes were clear to me, I just though that padding could be used for the niche.

For those looking for a longer discussion:

I wouldn't add a NonZeroU32 to the tuple because it would be confusing. If the size is important, then I would rather create a struct with the three fields and clearly document what the purpose of the NonZeroU32 is using field names and documentation.

I might use this type instead of NonZeroU32:

/// A type that can be used in place of padding
/// in a struct or tuple to give the padding a niche
/// for the Option optimization.
#[repr(u32)]
enum PaddingWithNiche {
    Padding = 1
}
2 Likes

Many thanks!
Actually my use-case was indeed for a struct with private fields, I just mentioned tuples for conciseness of the question.

do you happen to have a source for that? i'd like to dig into the specifics of rust layout guarantees, but i'm not able to find the relevant section of the rust reference.

https://rust-lang.github.io/unsafe-code-guidelines/glossary.html#padding

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.