64位Linux下Option<f64>为何占16字节?64位判别式意义何在?
Option<f64> take 16 bytes on 64-bit Linux? Great question—let's unpack this and address your confusion about discriminant size and compiler optimizations.
First, let's confirm your observation: on 64-bit Linux, Option<f64> does indeed occupy 16 bytes. Here's why:
No invalid bit patterns for
f64
Rust's compiler optimizesOption<T>to match the size ofTwhenever possible (this is called the "null pointer optimization"). For types like pointers or integers, there’s at least one invalid bit pattern (e.g., a null pointer for references, or an out-of-range value for some integers) that can be repurposed to representNone. Butf64is unique: every possible 64-bit combination counts as a valid floating-point value—including all NaN variants, which are still recognized as validf64values. There’s no spare bit pattern we can hijack to signalNone, so the compiler has to add a separate discriminant field to track whether theOptionisSome(f64)orNone.Discriminant size is tied to alignment rules
You’re right that we only need a tiny discriminant (even a single bit would technically work for two variants). But memory alignment gets in the way:f64requires 8-byte alignment on 64-bit systems. The discriminant field has to align with the largest field in the enum to maintain proper memory layout. If we tried using a 1-byte discriminant, the total size would be 8 + 1 = 9 bytes—but alignment rules would force the compiler to pad the struct to the next multiple of 8 bytes, resulting in 16 bytes anyway. Using an 8-byte discriminant avoids this explicit padding, but the final size ends up identical.Why not use a smaller discriminant with padding?
It’s not that the compiler "chooses" a 64-bit discriminant unnecessarily—rather, alignment constraints make the effective size the same either way. Rust’s enum layout rules prioritize alignment and consistency across all enum types, so using an 8-byte discriminant is the natural outcome here.Can we optimize this manually?
If you really needOption<f64>to fit in 8 bytes, you can leverage the fact that NaN has many distinct bit patterns. You can define a custom type that uses a specific NaN pattern to representNone, like this:#[repr(transparent)] struct OptionF64(f64); impl OptionF64 { // A unique NaN pattern we'll use to signal None const NONE_NAN: f64 = f64::from_bits(0x7FF8000000000001); fn none() -> Self { OptionF64(Self::NONE_NAN) } fn some(val: f64) -> Self { OptionF64(val) } fn is_none(&self) -> bool { self.0.to_bits() == Self::NONE_NAN.to_bits() } fn unwrap(&self) -> f64 { assert!(!self.is_none()); self.0 } }This works for most cases since real-world code rarely generates this exact NaN pattern, but it’s not universally safe—if your logic might produce this specific NaN, you’ll get false positives for
None. The compiler doesn’t do this automatically because it can’t guarantee safety for allf64use cases.
内容的提问来源于stack exchange,提问作者user1244932

