Can I rely on the flattened representation of a slice of arrays?

It appears to me that, for all T and n:

  • [T; n] and T have the same alignment
  • [T; n] is n times as large as T

I checked for i32, bool, f64, even ((), ()).

But can I rely on this for evil purposes for unsafe code?

use ::std::slice;
fn flatten_3d<T>(x: &[[T; 3]]) -> &[T] {
    unsafe { slice::from_raw_parts(x.as_ptr() as *const _, x.len() * 3) }
}

fn unflatten_3d<T>(x: &[T]) -> &[[T; 3]] {
    assert_eq!(x.len() % 3, 0);
    unsafe { slice::from_raw_parts(x.as_ptr() as *const _, x.len() / 3) }
}

fn main() {
    assert_eq!(unflatten_3d(&[0,1,2,3,4,5]), &[[0,1,2],[3,4,5]]);
    assert_eq!(flatten_3d(&[[0,1,2],[3,4,5]]), &[0,1,2,3,4,5]);
}

To elaborate:

I'm trying to find a type suitable for use in public APIs to represent a list of mathematical 3-vectors. I will likely need to multiply a matrix against them so I'll probably be using ndarrays at least interally at some point, but they feel a bit too heavy to use in my public interface. On the other hand, &[f64] (which I know is trivial to wrap as an ndarray) lacks key information from the type.

So I would really like to be able to use &[[f64; 3]] and Vec<[f64; 3]> in my interface; but I need to know that I can convert between these and flattened forms or ndarrays with O(1) cost.

2 Likes

I believe the answer is yes. According to the docs for slice, Slices are a view into a block of memory represented as a pointer and a length.

You could use mem::size_of_val to assert that your input and output slices in fact cover the same amount of memory.

From https://doc.rust-lang.org/nomicon/repr-rust.html:

A type's size must always be a multiple of its alignment. This ensures that an array of that type may always be indexed by offsetting by a multiple of its size.

1 Like

fwiw, ndarray does rely on that kind of transformation working in the by value sense of: [[T; n]; m] -> [T; m * n]. It's not used in any central code, but it's used in the array! macro.