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

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.