Array references from a slice with zero copy?


#1

Is there some way to create an array reference to a chunk of a larger data structure? As an example, suppose I write the function

fn from_le(x: &[u8]) -> u16 {
    x[0] as u16 + x[1] as u16 *8
}

This function has two disadvantages: it’s type does not document to the user that it needs a slice of size two, and at runtime the function needs to bounds check. If instead I write:

fn from_le(x: &[u8; 2]) -> u16 {
     x[0] as u16 + x[1] as u16 *8
 }

Then both of those limitations are removed, but I force the user to make a copy of a larger u8 array rather than operating in place, since a user cannot do something like:

let data: &[u8] = ..;
let n = from_le(&data[5..7]);

Is there a solution to this challenge? It seems like one ought to be able to create an array reference from a slice () with runtime bounds checking, of course), but I don’t see any way to do so in rust.

Any ideas? How can I document to humans and compiler the size of my inputs without forcing users to make copies? For other purposes we would use structs to avoid the need for copies but I’ve been working with encryption code that really cries for u8 slices or arrays.


#2

You could do something like this:

trait FixedSlice<T> {
    fn fixed_slice(&self) -> &T;
}

macro_rules! fixed_slice_impl {
    ($($ns:expr),* $(,)*) => {
        $(
            impl<T> FixedSlice<[T; $ns]> for [T] {
                fn fixed_slice(&self) -> &[T; $ns] {
                    assert_eq!(self.len(), $ns);
                    unsafe { ::std::mem::transmute(self.as_ptr()) }
                }
            }
        )*
    };
}

fixed_slice_impl! {
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
    10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
    20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
    30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
}

fn from_le(x: &[u8; 2]) -> u16 {
    x[0] as u16 + x[1] as u16 *8
}

fn main() {
    let data: &[u8] = &[1, 8, 53, 72, 191, 4, 0, 201, 32, 48];
    let n = from_le((&data[5..7]).fixed_slice());
    println!("n: {}", n);
}

You could eliminate one additional range check by having fixed_slice take the actual slice range and re-implement the slicing logic, but I was feeling lazy.


#3

Thanks, @DanielKeep for that suggestion. Based on your code, I have gone ahead and created a macro crate with two macros that grab an array reference from something sliceable. I am not really happy with the API (which requires that users specify the element type), but it seems to work, and hopefully works efficiently. It’s available at https://github.com/droundy/arrayref, and on crates.io as arrayref.