Don't lie to the compiler

Just thought I'd post this reminder that the null-pointer optimization exists and if you're not careful (like I wasn't) you can create some very confusing situations. I guess the lesson is not to lie to the compiler.

use std::{ptr, slice};

fn main() {
    // Should be fine because the pointer's never dereferenced because the
    // length is zero, or so you'd think.
    let slice: &[u8] = unsafe {
        slice::from_raw_parts(ptr::null(), 0)
    };

    // It's obviously okay!
    let obviously_okay: Result<_, ()> = Ok(slice);
    
    // Or not.
    println!("Is it okay? {:?}", obviously_okay.is_ok());

    // => Is it okay? false
}
3 Likes

I did not expect that to kick in for Result<NonzeroType, ()>. That could really bite someone. Is this gotcha documented anywhere?

The documentation for std::slice::from_raw_parts outright says.

data must be non-null and aligned even for zero-length slices. One reason for this is that enum layout optimizations may rely on references (including slices of any length) being aligned and non-null to distinguish them from other data. You can obtain a pointer that is usable as data for zero-length slices using NonNull::dangling() .

10 Likes

I opened the thread expecting a flame war :fire::smiling_imp::fire:

8 Likes