The correct way to generate null pointers to `?Sized` types?

Background: I'm writing a garbage collector which needs a sentinel value for "dead" pointers. In order to implement CoerceUnsized, I can't use Option<NonNull<T> to represent my underlying raw pointer, so I use *const T instead. However, if T is ?Sized, it's impossible to create a null pointer to T using std::ptr::null(). In particular, all I really care about is whether my null pointer returns true when is_null() is called on it.

Is there a way to create one in a more roundabout way?

My first guess was to do something like this:

unsafe fn make_my_null_pointer<T: ?Sized>() {
    let ptr: *const T = MaybeUninit::zeroed().assume_init();
}

However, this is wildly unsafe. Is there a method which is more blessed to do so?

Having a *const dyn Trait with a vtable pointer that doesn't point to a valid vtable for Trait is UB unless they decide to make it not-UB (and this issue wants to keep it UB). So I don't think this is soundly possible in full generic-osity currently.

Miri flags it as such too.

1 Like

I went back and skimmed the linked issue, and it passed FCP (but just hasn't been closed yet), so I don't think it will be possible anytime soon.

Would it be possible to create a null pointer from an existing valid pointer, though? In my case, I can use an existing pointer with a valid vtable - can I construct one with an address part of null and a vtable of the correct variety?

Having Option<NonNull<T>> works just well.

Here's the main issue. I can't implement CoerceUnsized on a pointer type containing Option<NonNull<T>>, unless there's some method to do so that I haven't heard of.

The layout of *const dyn Trait is not guaranteed, so you still need some other way of deconstructing and constructing. Those are part of the ptr_metadata feature:

  • *const MaybeUnsized as *const () for a pointer to the data if you need it (you don't for NULL)
  • ptr::metadata for the metadata
  • ptr::from_raw_parts to reconstruct
1 Like

Well, pondering this a bit, I guess you could create a fallible version...

  • if your pointer is thin (one usize), just use NULL (via transmute_copy probably)
  • otherwise, check that your wide pointer is two usize
    • Else bail
  • transmute it to [usize; 2]
  • compare against *const _ as *const () as usize to see if it's unambiguous which of the two array entries is the metadata
    • Bail if ambiguous
  • set the data half to 0
  • transmute it back

Didn't try it myself but probably this needs transmute_copy and there are some other assumptions, like pointer metadata is always initialized and thus valid to read for the sake of comparison.

1 Like