Soundness of reinterpreting immutable references to transmute compatible types

Is it sound to transmute &B to &A when A and B have the same known layout, alignment valid bit patterns?

I found validity.reference-box but it's phrased:

  • A reference or Box<T> must be aligned and non-null, it cannot be dangling, and it must point to a valid value (in case of dynamically sized types, using the actual dynamic type of the pointee as determined by the metadata). Note that the last point (about pointing to a valid value) remains a subject of some debate.

Where do I find the definition of a "valid value"? Since A and B satisfy the rules for transmutation (both ways), does that mean that "valid values" of one are valid values of another with this definition of valid value?

use std::ptr::NonNull;

#[derive(Debug)]
#[repr(transparent)]
struct A(*mut u8);

#[derive(Debug)]
#[repr(transparent)]
struct B(Option<NonNull<u8>>);

impl From<A> for B {
    fn from(value: A) -> Self {
        Self(NonNull::new(value.0))
    }
}

impl std::ops::Deref for B {
    type Target = A;
    
    fn deref(&self) -> &Self::Target {
        // SAFETY: no idea
        // https://doc.rust-lang.org/reference/behavior-considered-undefined.html#r-undefined.validity.reference-box
        // is 
        unsafe { &*std::ptr::from_ref(self).cast() }
    }
}

fn main() {
    let a = A(std::ptr::dangling_mut());
    
    let b = B::from(a);
    
    // Conveniently auto-derefs.
    let r: &A = &b;
    
    dbg!(r);
}

Playground

This must have been asked already but I struggled to find this particular immutable reference casting of transmute compatible types.

(You need to click the Share button to get a proper playground URL.)

Your example is fine.

However, one can construct counter-examples. E.g. transmuting lifetimes is generally unsound even if nothing else about the types differ; transmuting between dyn pointers is generally unsound; etc. Custom types may also have their own custom safety invariants.

It's hard to make correct blanket statements about when transmutes are safe.

It's the section you quoted.

Here's another resource.

You can sometimes soundly transmute (as in transmute) between A and B, but not &A and &B, due to alignment (since transmute moves the bits). One could probably cook up other examples too.

4 Likes

(w.r.t the link, I clicked the link itself instead of the clipboard icon, fixed)

If A and B have the same bit-validity then do any custom safety invariants necessarily uphold? I guess not if those invariants are dependent on data stored elsewhere.

I'm interested in an example for learning purposes, but my main question was answered, thanks!

Also note this "elsewhere" might not be even on the same machine that runs the code.

Run Rust Playground under Miri.

1 Like

Your example casts references &A to &B but A and B are not transmute compatible, because of the difference in alignment, so it's not a counter example as far as I understand.

Oops, my mental model of transmute was completely wrong. The docs mention

Because transmute is a by-value operation, alignment of the transmuted values themselves is not a concern. As with any other function, the compiler already ensures both Src and Dst are properly aligned. However, when transmuting values that point elsewhere (such as pointers, references, boxes…), the caller has to ensure proper alignment of the pointed-to values.

I do wonder though if you can count on the layout of:

#[align(16)]
struct A([u8; 16])

and

[u8; 16]

being the same without #[repr(transparent)] or #[repr(C)]. Type layout - The Rust Reference suggests that apart from fields not overlapping, having an offset multiple of their alignment, they may be reordered and the layout is otherwise unspecified. With one field of a type without holes the actual layout is probably what you expect but it may not be? So even thought the by-value transmute passes miri it may not be sound?

True; #[repr(C, align(16))] on A would be better.