Best way to cast Option<&T> to *const T

Inserting a transmute call affects the program. You're ascribing a safe behavior to transmute for invalid values that it doesn't have.

See the documentation:

Both the argument and the result must be valid at their given type. Violating this condition leads to undefined behavior. The compiler will generate code assuming that you, the programmer, ensure that there will never be undefined behavior . It is therefore your responsibility to guarantee that every value passed to transmute is valid at both types Src and Dst . Failing to uphold this condition may lead to unexpected and unstable compilation results. This makes transmute incredibly unsafe . transmute should be the absolute last resort.

1 Like

The key part it, actually, here:

The answer is, of course, is based on the fact that compiler have no intuition and it also couldn't understand what correct program is suppose to do.

Compiler just applies bunch of optimisation passes and that's it.

Now, compiler developers have intuition and understand what correct program is supposed to do.

Unfortunately they coined the name “undefined behavior” for “something that valid program shouldn't ever do” and this causes us no end of grief.

Because it's “behavior” (even if “undefined”) people assume it's something “allowed but weird”… and that is where whole confusion starts.


I have rewritten the code:

fn foo<T: ?Sized>(src: Option<&T>) -> *const T
where <T as core::ptr::Pointee>::Metadata: Default
    src.map_or(core::ptr::from_raw_parts(core::ptr::null(), <<T as core::ptr::Pointee>::Metadata as Default>::default()), |x| x as *const T)

Now, @H2CO3's code works as expected

fn main() {
    let x = foo::<str>(None);
    let n = (x as *const [u8]).len();
    if n == 0 {
        println!("hello {n}"); // hello 0

In case of an SST, it generates the same asm. In case of a DST, we get two more mnemonics:

mov rdx, rsi
mov rax, rdi

test rdi, rdi ; if address == 0
cmove rdx, rdi ; replace metadata with address

And to avoid nightly features, it's possible to write this like that. Note that this is more restrictive than using std::ptr::Pointee::Metadata (it only supports slices and strings), but it has an advantage of working on stable.

use std::ptr;

const fn ptr_from_option<T>(option: Option<&T>) -> *const T
    T: ?Sized + NullPtr,
    if let Some(x) = option {
    } else {

trait NullPtr {
    const NULL_PTR: *const Self;

impl<T> NullPtr for T {
    const NULL_PTR: *const T = ptr::null();

impl<T> NullPtr for [T] {
    const NULL_PTR: *const [T] = ptr::slice_from_raw_parts(ptr::null(), 0);

impl NullPtr for str {
    const NULL_PTR: *const str = <[u8]>::NULL_PTR as *const str;

fn main() {
    dbg!(ptr_from_option(Some(&[1, 2, 3])));
1 Like

Is null a valid vtable? This is not obvious. I think the consensus is that it is not UB to produce a trait object pointer with a null vtable, but it is unsound to expose it to safe code.

Also, the layout of wide pointers is not guaranteed, so you cannot rely on all-zeroes being a valid bit pattern for them.

1 Like

This is UB for dyn types. The following program:

fn foo<T: ?Sized>(src: Option<&T>) -> *const T {
    src.map_or(unsafe {core::mem::zeroed()}, |x| x as *const T)

fn main() {
    foo::<dyn std::any::Any>(None);

Will print the following:

thread 'main' panicked at 'attempted to zero-initialize type `*const dyn core::any::Any`, which is invalid', src/

Don't do that, trait object pointers are basically this:

struct TraitObject{
    ptr: *const (),
    vtable: &'static VTable,

Null vtables are instant UB.

1 Like

AFAIK the validity vs. safety invariants are not yet decided for vtables, but treating that as UB is the conservative bet in the meantime.

1 Like

My bad. Thought &dyn Any is (*const (), &'static VTable) and *const dyn Any is (*const (), *const VTable).

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.