Nazinho
Nazinho

Reputation: 321

Is it legal to cast a struct to an array?

Consider the following:

// Just a sequence of adjacent fields of same the type
#[repr(C)]
#[derive(Debug)]
struct S<T> {
    a : T,
    b : T,
    c : T,
    d : T,
}

impl<T : Sized> S<T> {
    fn new(a : T, b : T, c : T, d : T) -> Self {
        Self {
            a,
            b,
            c,
            d,
        }
    }
    // reinterpret it as an array
    fn as_slice(&self) -> &[T] {
        unsafe { std::slice::from_raw_parts(self as *const Self as *const T, 4) }
    }
}

fn main() {
    let s = S::new(1, 2, 3, 4);
    
    let a = s.as_slice();
    
    println!("s :: {:?}\n\
              a :: {:?}", s, a);
}

Upvotes: 3

Views: 1175

Answers (1)

pigeonhands
pigeonhands

Reputation: 3424

Yes, it is safe and portable, except for very large T (fix below). None of the points listed in the safety section of the documentation for std::slice::from_raw_parts are a concern here:

  • The data pointer is valid for mem::size_of::<T>() * 4, which is the size of S<T>, and is properly aligned.

    • All of the items are in the same allocation object, because they are in the same struct.
    • The pointer is not null, because it is a cast from the safe &self parameter, and it is properly aligned, because S<T> has (at least) the alignment of T.
  • The data parameter definitely points to 4 consecutive initialized Ts, because S is marked #[repr(C)] which is defined such that in your struct, no padding would be introduced. (repr(Rust) makes no such guarantee).

  • The memory referenced is not mutated during the lifetime of the reference, which is guaranteed by the borrow checker.

  • The total size of the slice must not be greater than isize::MAX. The code does not check this, so it is technically a safety hole. To be sure, add a check to as_slice, before the unsafe:

    assert!(std::mem::size_of::<S<T>>() <= isize::MAX as _);
    

    The check will normally be optimized out.

Upvotes: 5

Related Questions