Transmutting with Generic Constants

Hello, I was wondering why the following would not be correctly checked as valid. I'm about a few months into my Rust journey, of which I had many challenges regarding arrays in general, but this one seems to be a compiler "issue".

/// Upon asking this question, I realized this was not random, but question still remains.
fn make_random<const R : usize, const C : usize>() -> ArrayStorage<f32, R, C> {
    unsafe { return transmute([[fastrand::f32(); R]; C]); }
}

cannot transmute between types of different sizes, or dependently-sized types [E0512]
Note: source type: [[f32; R]; C] (this type does not have a fixed size)
Note: target type: nalgebra::ArrayStorage<f32, R, C> (size can vary because of [[f32; R]; C])

/// An array-based statically sized matrix data storage.
#[repr(transparent)]
/// Some more annotations
pub struct ArrayStorage<T, const R: usize, const C: usize>(pub [[T; R]; C]);

Why would the size vary when it can be checked at compile time and is effectively identical?

The size of ArrayStorage<f32, R, C> and [[f32; R]; C] is not fixed in the sense that you cannot say their size is 8 or 16 or something else. Different values for R and C will result in different sizes.

Talking about "compile time" here is a bit misleading, since there isn't a single "compile time". There is a time where the generic function is checked, where R and C are not known, and a time where it is instantiated with some specific values for R and C. In the former the compiler cannot know the actual sizes of the two types because it must consider all possible values for R and C, while the latter is not even guaranteed to happen (it could happen only when compiling a dependent crate, and won't happen when running cargo check).

The general way to fix this issue is to use the even unsafer std::mem::transmute_copy

fn make_random<const R : usize, const C : usize>() -> ArrayStorage<f32, R, C> {
    // SAFETY: ArrayStorage is transparent over `[[f32; R]; C]` and `[[f32; R]; C]` doesn't implement `Drop`
    unsafe { transmute_copy(&[[fastrand::f32(); R]; C]); }
}

Note that comment about implementing drop. If it was some other type that implemented Drop you should have wrapped it in a std::mem::ManuallyDrop, for example:

fn make_random<const R : usize, const C : usize>() -> ArrayStorage<f32, R, C> {
    // SAFETY: ArrayStorage is transparent over `[[f32; R]; C]` and `ManuallyDrop` prevents
    // the dropping of the source
    unsafe { transmute_copy(&ManuallyDrop::new([[fastrand::f32(); R]; C]); })
}

However in your case all of this is actually unnecessary. ArrayStorage has a single public field of type [[T; R]; C] so you can just initialize it by populating that field!

fn make_random<const R : usize, const C : usize>() -> ArrayStorage<f32, R, C> {
    // Look ma', no unsafe!
    ArrayStorage([[fastrand::f32(); R]; C])
}
4 Likes

Thank you for the comprehensive response. Sometimes, the simplest solution was right in front of you and you miss it completely!

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.