Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I use the "null pointer optimization" for my own non-pointer types?

When you have an Option<&T>, the compiler knows that NULL is never a possible value for &T, and encodes the None variant as NULL instead. This allows for space-saving:

use std::mem;

fn main() {
    assert_eq!(mem::size_of::<&u8>(), mem::size_of::<Option<&u8>>());
}

However, if you do the same with a non-pointer type, there's no extra bits to store that value in and extra space is required:

use std::mem;

fn main() {
    // fails because left is 1 and right is 2
    assert_eq!(mem::size_of::<u8>(), mem::size_of::<Option<u8>>()); 
}

In general, this is correct. However, I'd like to opt-in to the optimization because I know that my type has certain impossible values. As a made-up-example, I might have a player character that has an age. The age may be unknown, but will never be as high as 255:

struct Age(u8);

struct Player {
    age: Option<Age>,
}

I'd like to be able to inform the optimizer of this constraint - Age can never be 255, so it's safe to use that bit pattern as None. Is this possible?

like image 447
Shepmaster Avatar asked May 23 '15 14:05

Shepmaster


1 Answers

As of Rust 1.28, you can use std::num::NonZeroU8 (and friends). This acts as a wrapper that tells the compiler the contents of a number will never contain a literal zero. It's also why Option<Box<T>> is pointer-sized.

Here's an example showing how to create an Age and read its payload.

use std::num::NonZeroU8;  struct Age(NonZeroU8);  impl Age {     pub fn new(age: u8) -> Age {         let age = NonZeroU8::new(age).expect("Age cannot be zero!");         Age(age)     }      pub fn age(&self) -> u8 {         self.0.get()     } }  struct Player {     age: Option<Age>, }  fn main() {     println!("size: {}", std::mem::size_of::<Player>());     // Output: size: 1 } 
like image 132
DK. Avatar answered Sep 21 '22 17:09

DK.