Is it safe to assume that the `None` variant of `Option<NonNull<dyn T>>` is equivalent to `mem::zeroed()`?

I've been implementing a semi-concurrent hash-map for storing single values of different types, where each value is stored in an Option, as above. Using alloc_zeroed(), would I still have to manaully initialise each bucket in the map with None?

No, the vtable part of the fat pointer in Option<NonNull<dyn T>> is completely unspecified.

1 Like

Does that mean the Option may match on the Some case, even if it was created using using alloc_zeroed()?

None is only guaranteed to be zeros according to the table in the representation docs, which includes NonNull<U> only for U: Sized.

2 Likes

No, it won't match Some however it may also not match None for example, if it requires the vtable pointer in the fat pointer to be non-null. (I think it currently would match None, however there are no guarantees that this continues to work into the future)

Ah, thank you.

Note that you’re not allowed to use a zeroed *mut dyn Trait, either:

fn main() {
    let p: *mut dyn core::fmt::Debug = unsafe { core::mem::zeroed() };
    println!("{p:p}");
}
warning: the type `*mut dyn std::fmt::Debug` does not permit zero-initialization
 --> src/main.rs:2:49
  |
2 |     let p: *mut dyn core::fmt::Debug = unsafe { core::mem::zeroed() };
  |                                                 ^^^^^^^^^^^^^^^^^^^
  |                                                 |
  |                                                 this code causes undefined behavior when executed
  |                                                 help: use `MaybeUninit<T>` instead, and only call `assume_init` after initialization is done
  |
  = note: the vtable of a wide raw pointer must be non-null
  = note: `#[warn(invalid_value)]` on by default


thread 'main' panicked at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/panicking.rs:225:5:
aborted execution: attempted to zero-initialize type `*mut dyn std::fmt::Debug`, which is invalid
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

I believe it is currently undecided whether, but likely to be true in the future that, the vtable pointer needs to point to a valid vtable in addition to being non-null.

The vtable portion of NonNull<dyn T>> (the contained *const dyn T) has a safety requirement of being a valid vtable pointer, and a validity requirement of being word-aligned and non-null.

With trait upcasting stabilized, I don't think there's any going back on the safety requirement.

1 Like

Interestingly, this doesn't produce the same error:

let a: Option<*mut dyn Debug> = unsafe { mem::zeroed() };

That's because it (currently, NOT guaranteed) uses the non-nullness of the vtable to niche-encode the None.

You can see this clearly in the LLVM IR: https://rust.godbolt.org/z/b5a3nhaWP

3 Likes