Learn Rust Snippet Modification

I've been following along the Learn Rust the Dangerous Way
and I see in third advance a snippet that double casts a Rust reference to a pointer, with the comment Note 1

for i in 0..ROUNDED_INTERACTIONS_COUNT/2 {
    let mut position_Delta =
        [mem::MaybeUninit::<__m128d>::uninit(); 3];
    for m in 0..3 {
        position_Delta[m].as_mut_ptr().write(
            *(&position_Deltas[m].0
                as *const f64         // <----- Note 1
                as *const __m128d).add(i)
        );
    }
    let position_Delta: [__m128d; 3] =
        mem::transmute(position_Delta);

The attached note says

In Rust, we do the same thing. Rust casts primitive types using as. Note that there is one more cast in Rust than in C (Note 1). This is because the expression &x in Rust takes the address of x as a reference, but we want a raw pointer — so we cast first to a pointer, and then to a pointer of a different type.
So this lead me to consider 2 things,

  • Is this not considered to be UB?

Unsafe code sometimes has to deal with pointers that may dangle, may be misaligned, or may not point to valid data. A common case where this comes up are repr(packed) structs. In such a case, it is important to avoid creating a reference, as that would cause undefined behavior. This means the usual & and &mut operators cannot be used, as those create a reference -- even if the reference is immediately cast to a raw pointer, it's too late to avoid the undefined behavior[1]

  • Is the new &raw syntax in Rust 1.82.0 a valid alternative double cast in the code snippet above?

  1. Rust 1.82.0 Release Notes - Native Syntax for Creating a Raw Pointer ↩︎

Is this not considered to be UB?

It's not UB to cast a reference to a raw pointer; this is a normal thing to do code. You can only cause UB by misusing the raw pointer in unsafe code.

It's not UB to cast a pointer to a different type, or even to read or write through that pointer — as long as the layout and validity invariants of the two types are compatible for the specific reinterpretation you are doing. So, for example, if you cast a &mut f64 to *mut [u8; 8], then you can freely use the result, because it’s valid to treat any sequence of 8 bytes as a f64. However, if you cast an &mut bool to *mut u8, then you must not write anything but 0 or 1 to that pointer, or you’ve violated the validity invariant of bool.

  • Is the new &raw syntax in Rust 1.82.0 a valid alternative double cast in the code snippet above?

&raw replaces the reference-to-raw-pointer cast but not the pointee type cast, but pointer::cast() is a more specific way to change the pointee type than as. So, using both of them,

(&raw const position_Deltas[m].0).cast::<__m128d>()

would get the same pointer in a more straightforward and precise fashion.

2 Likes

Thanks for the clarification, I thought that converting references to pointers as a whole was an issue from the quote "even if the reference is immediately cast to a raw pointer, it's too late to avoid the undefined behavior", but i.g that is specific to the state of the pointer and the data which it is pointing and not a general rule.
I tried augmenting the code with something like this

        for m in 0..3 {
            position_Delta[m].as_mut_ptr().write(
            *(&raw const position_Deltas[m].0 as *const __m128d
            ).add(i));a
        }

Is this approach similar to the pointer::cast you provided I am assuming that as *const m128d is the pointee type cast you mentioned?

You lost the .0 field access, which may or may not matter, but other than that, yes.

I personally recommend avoiding the as operator whenever an alternative exists, because it does too many different things.

1 Like

I understand much better now, also I edited the .0 field access back since there wasn't any particular need to remove it