What's the best way to convert a [[T; 4]; 3] into a [T; 12]?
Asked Answered
M

2

6

As I understand it, a [[T; 4]; 3] and a [T; 12] have the same layout in memory. What is the best way to convert a value between these types? Can I convert a reference to one into a reference to the other? Can I avoid copying all the elements? Do I need unsafe?

Matadi answered 13/12, 2016 at 1:30 Comment(0)
R
9

Yes, you can convert a reference to a [[T; 4]; 3] into a reference to a [T; 12], but only with unsafe code, using mem::transmute. It's best to wrap this in a function so that the resulting reference is assigned the proper lifetime, as otherwise transmute would make it possible to obtain a reference with a larger lifetime than the reference should have.

fn convert<'a>(a: &'a [[u8; 4]; 3]) -> &'a [u8; 12] {
    unsafe { std::mem::transmute(a) }
}

This can be shortened thanks to the lifetime elision rules:

fn convert(a: &[[u8; 4]; 3]) -> &[u8; 12] {
    unsafe { std::mem::transmute(a) }
}

Though when dealing with unsafe code, I'd understand if you preferred the more explicit version!

Raffinate answered 13/12, 2016 at 2:10 Comment(3)
Just to clarify: This does a bit-for-bit copy if the reference itself and not the contents of the array? I wasn't sure.Bonds
@Simon: If you transmute a reference to the array, then only the reference is copied. If you transmute the array directly (as you did in your answer), then the whole array is copied. However, the compiler might elide the copy depending on what you do with the value and on how you compile the code (debug or release).Levitate
Great no problem. I will leave my answer anyway as a point of reference for myself in future. Thanks for clarifying :)Bonds
B
3

Disclaimer: I am not really great with Rust's low-level side of things yet, I don't know what is considered "good practice" in low-level Rust. The advice given here may not be good ideas. I'm putting them here though because... well, they work.

You could transmute them. The problem is that it'll be a copy, the documentation says it's the equivalent of a memcpy call. This isn't what you wanted, but here it is anyway:

fn main() {
    let a: [[u8; 4]; 3] = [[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]];
    let b: [u8; 12] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];

    println!("a: {:?}", a);
    println!("b: {:?}", b);

    let c = unsafe { std::mem::transmute::<[[u8; 4]; 3], [u8; 12]>(a) };

    println!("c: {:?}", c);
}

Your other option is to work with a raw pointer:

fn main() {
    let a: [[u8; 4]; 3] = [[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]];
    let b: [u8; 12] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];

    println!("a: {:?}", a);
    println!("b: {:?}", b);

    let c = &a as *const _ as *const [u8; 12];
    // Or it can be this: let c = &mut a as *mut [[u8; 4]; 3];

    for i in 0..12 {
        let p = c as *const u8;
        let v = unsafe { *p.offset(i) };
        println!("{}", v);
    }
}

which isn't particularly great either.

The pointer can also be a pointer or mutable pointer to the same type (since &mut T can be cast to *mut T), and the above code works exactly the same (with a marked mutable):

let c = &mut a as *mut [[u8; 4]; 3];

I do wonder whether this is a bit of an XY-Problem though. Maybe the way you're working with your data can be altered to not require this?

Bonds answered 13/12, 2016 at 1:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.