Dmitry
Dmitry

Reputation: 1647

Is it safe to cast `struct` to a slice in rust?

I have some struct, something like this:

struct MyStruct<T> {
    field1: T,
    field2: T,
    field3: T,
}

What I know and really sure about the struct:

In my project it might be useful to access structs like this like a slices. So what I've done:

impl<T> AsRef<[T]> for MyStruct<T> {
    fn as_ref(&self) -> &[T] {
        let ptr = self as *const Self;
        let ptr2 = ptr as *const T;
        unsafe { std::slice::from_raw_parts(ptr2, 3) }
    }
}

Now I can access fields of MyStruct like this:

let s = MyStruct { field1: 1.0, field2: 2.0, field3: 3.0 };
s.as_ref()[1]

I've tested some examples both in dev and release modes and didn't find any errors. Still I'm not sure about this kind of pointer magic.

Rust cannot guarantee that memory layout will preserve an order of the fields. On the other hand, as far as I know, it happens only when struct has fields with different sizes and need to be aligned.

So I'm really curious:

  1. Are there any rust safe guarantee violations?
  2. Is there any safe or performance difference between this variant and the next one?
  3. Should I use a union instead of this trick?
impl<T> AsRef<[T]> for MyStruct<T> {
    fn as_ref(&self) -> &[T] {
        let tmp: &[T; 3] = unsafe { std::mem::transmute(self) };
        tmp.as_ref()
    }
}

Upvotes: 2

Views: 741

Answers (1)

Peter Hall
Peter Hall

Reputation: 58805

  1. Are there any rust safe guarantee violations?

Yes, the order is not guaranteed. Even though it's unlikely to change, Rust's ABI is not stable so a future version of the Rust compiler could do things differently.

You can force the C ABI to be used instead, which will make it safe:

#[repr(C)]
struct MyStruct<T> {
    field1: T,
    field2: T,
    field3: T,
}

A possibly better approach, avoiding unsafe, would be to store the fields in an array and then provide accessors instead:

struct MyStruct<T> {
    fields: [T; 3],
}

impl<T> MyStruct<T> {
    fn field1(&self) -> &T {
        &self[0]
    }

    fn field1_mut(&mut self) -> &mut T {
        &mut self[0]
    }

    // etc, you could generate these with a macro if there are a lot
}
  1. Is there any safe or performance difference between this variant and the next one?

These are likely to compile to the same thing. If you aren't sure then test. For simple things you can pase code into rust.godbolt.org and compare the assembly.

  1. Should I use Union instead of this trick?

That would also only be safe with #[repr(C)]. I can't see any obvious benefit to using a union here, but it would mean you'd have to use even more unsafe code, so it doesn't seem like a good idea.

Upvotes: 3

Related Questions