Search code examples
arraysoptimizationrustreferencemutable

How to get a working mutable reference to a subset of an array?


This works as expected for reading:

fn u64_from_low_eight(buf: &[u8; 9]) -> u64 {
    let bytes: &[u8; size_of::<u64>()] = buf[..size_of::<u64>()].try_into().unwrap();
    u64::from_le_bytes(*bytes)
}

(It nicely optimises into a single assembly instruction on AArch64 and x86_64.)

I had expected something similar to work for an unaligned write of a u64 to a buffer.

/// Encodes a u64 into 1-9 bytes and returns the number of bytes updated.
pub fn encode(value: u64, buf: &mut [u8; 9]) -> usize {
    let low64: &mut [u8; size_of::<u64>()] = &mut buf[..(size_of::<u64>())].try_into().unwrap();

    match value {
        // FIXME: Change to exclusive ranges once the feature's stabilised.
        OFFSET0..=OFFSET1_LESS_ONE => {
            let num = inner_encode::<1>(value, low64);
            #[cfg(test)] eprintln!("low64: {low64:?}");
            #[cfg(test)] eprintln!("buf: {buf:?}");

            num
        },

low64 (above) appears not to be a mutable reference into the first eight bytes of buf. (Perhaps it is pointing at a copy?)

i.e. low64 and the first eight bytes of buf are different in the example above.

What can I use instead of let low64: &mut [u8; size_of::<u64>()] = &mut buf[..(size_of::<u64>())].try_into().unwrap(); to get a &mut [u8; 8] which points at the first eight bytes of a &mut [u8; 9]?

(My intention is that this should also optimise into a single unaligned write on AArch64 and x86_64.)


Update: Here's the problem on the playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=294a9dd9ba1400eceb2d945a10028a6b

use std::mem::size_of;

fn u64_to_low_eight(value: u64, buf: &mut [u8; 9]) {
    let low64: &mut [u8; size_of::<u64>()] = &mut buf[..size_of::<u64>()].try_into().unwrap();
    *low64 = u64::to_le_bytes(value);    
    dbg!(low64);
}

fn main() {
    let mut src: [u8; 9] = [1, 2, 3, 4, 5, 6, 7, 8, 9];
    u64_to_low_eight(0x0A_0B_0C_0D_0E_0F_10_11, &mut src);
    dbg!(src);
}

and the output, where I want src to change too:

   Compiling playground v0.0.1 (/playground)
    Finished dev [unoptimized + debuginfo] target(s) in 0.62s
     Running `target/debug/playground`
[src/main.rs:6] low64 = [
    17,
    16,
    15,
    14,
    13,
    12,
    11,
    10,
]
[src/main.rs:12] src = [
    1,
    2,
    3,
    4,
    5,
    6,
    7,
    8,
    9,
]

Solution

  • You need to wrap &mut buf[..size_of::<u64>()] in parenthesis. &mut is a low-priority operator, so everything to the right is evaluated first, making your code equivalent to:

    // takes a mutable reference to those 8 copied bytes
    let low64: &mut [u8; size_of::<u64>()] = &mut (
        // copies 8 bytes out of `buf`
        buf[..size_of::<u64>()].try_into().unwrap()
    );
    

    So you need to do the following instead:

    let low64: &mut [u8; size_of::<u64>()] = (&mut buf[..size_of::<u64>()]).try_into().unwrap();