Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Splitting owned array into owned halves

Tags:

arrays

rust

I would like to divide a single owned array into two owned halves—two separate arrays, not slices of the original array. The respective sizes are compile time constants. Is there a way to do that without copying/cloning the elements?

let array: [u8; 4] = [0, 1, 2, 3];

let chunk_0: [u8; 2] = ???;
let chunk_1: [u8; 2] = ???;

assert_eq!(
  [0, 1],
  chunk_0
);
assert_eq!(
  [2, 3],
  chunk_1
);

Since it would amount to merely moving ownership of the elements, I have a hunch there should be a zero-cost abstraction for this. I wonder if I could do something like this with some clever use of transmute and forget. But there are a lot of scary warnings in the docs for those functions.

My main motivation is to operate on large arrays in memory without as many mem copies. For example:

let raw = [0u8; 1024 * 1024];

let a = u128::from_be_array(???); // Take the first 16 bytes
let b = u64::from_le_array(???); // Take the next 8 bytes
let c = ...

The only way I know to accomplish patterns like the above is with lots of mem copying which is redundant.

like image 300
Matt Thomas Avatar asked Jun 11 '26 21:06

Matt Thomas


2 Answers

You can use std::mem:transmute (warning: unsafe!):

fn main() {
    let array: [u8; 4] = [0, 1, 2, 3];

    let [chunk_0, chunk_1]: [[u8; 2]; 2] =
        unsafe { std::mem::transmute::<[u8; 4], [[u8; 2]; 2]>(array) };

    assert_eq!([0, 1], chunk_0);
    assert_eq!([2, 3], chunk_1);
}

Playground

like image 143
Netwave Avatar answered Jun 14 '26 18:06

Netwave


use std::convert::TryInto;

let raw = [0u8; 1024 * 1024];
    
let a = u128::from_be_bytes(raw[..16].try_into().unwrap()); // Take the first 16 bytes
let b = u64::from_le_bytes(raw[16..24].try_into().unwrap()); // Take the next 8 bytes

In practice, I've found the compiler is pretty smart about optimizing this. With optimizations, it will do the above in a single copy (directly into the register that holds a or b, respectively). As an example, according to godbolt, this:

use std::convert::TryInto;

pub fn cvt(bytes: [u8; 24]) -> (u128, u64) {
    let a = u128::from_be_bytes(bytes[..16].try_into().unwrap()); // Take the first 16 bytes
    let b = u64::from_le_bytes(bytes[16..24].try_into().unwrap()); // Take the next 8 bytes
    (a, b)
}

with -C opt-level=3 compiles into:

example::cvt:
        mov     rax, qword ptr [rdi + 8]
        bswap   rax
        mov     rdx, qword ptr [rdi]
        bswap   rdx
        mov     rcx, qword ptr [rdi + 16]
        ret

It's optimized out any extra copies, calling the try_into method, possibly panicking, et cetera.

like image 23
lkolbly Avatar answered Jun 14 '26 16:06

lkolbly



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!