Take slice of certain length known at compile time

2019-07-25 06:51发布

In this code:

fn unpack_u32(data: &[u8]) -> u32 {
    assert_eq!(data.len(), 4);
    let res = data[0] as u32 |
    (data[1] as u32) << 8 |
    (data[2] as u32) << 16 |
        (data[3] as u32) << 24;
    res
}

fn main() {
    let v = vec![0_u8, 1_u8, 2_u8, 3_u8, 4_u8, 5_u8, 6_u8, 7_u8, 8_u8];
    println!("res: {:X}", unpack_u32(&v[1..5]));    
}

the function unpack_u32 accepts only slices of length 4. Is there any way to replace the runtime check assert_eq with a compile time check?

标签: rust
3条回答
Fickle 薄情
2楼-- · 2019-07-25 07:37

I think the answer is no as it is; a slice doesn't have a size (or minimum size) as part of the type, so there's nothing for the compiler to check; and similarly a vector is dynamically sized so there's no way to check at compile time that you can take a slice of the right size.

The only way I can see for the information to be even in principle available at compile time is if the function is applied to a compile-time known array. I think you'd still need to implement a procedural macro to do the check (so nightly Rust only, and it's not easy to do).

If the problem is efficiency rather than compile-time checking, you may be able to adjust your code so that, for example, you do one check for n*4 elements being available before n calls to your function; you could use the unsafe get_unchecked to avoid later redundant bounds checks. Obviously you'd need to be careful to avoid mistakes in the implementation.

查看更多
贪生不怕死
3楼-- · 2019-07-25 07:39

Yes, kind of. The first step is easy: change the argument type from &[u8] to [u8; 4]:

fn unpack_u32(data: [u8; 4]) -> u32 { ... }

But transforming a slice (like &v[1..5]) into an object of type [u8; 4] is hard. You can of course create such an array simply by specifying all elements, like so:

unpack_u32([v[1], v[2], v[3], v[4]]);

But this is rather ugly to type and doesn't scale well with array size. So the question is "How to get a slice as an array in Rust?". I used a slightly modified version of Matthieu M.'s answer to said question (playground):

fn unpack_u32(data: [u8; 4]) -> u32 {
    // as before without assert
}

use std::convert::AsMut;

fn clone_into_array<A, T>(slice: &[T]) -> A
    where A: Default + AsMut<[T]>,
          T: Clone
{
    assert_eq!(slice.len(), std::mem::size_of::<A>()/std::mem::size_of::<T>());

    let mut a = Default::default();
    <A as AsMut<[T]>>::as_mut(&mut a).clone_from_slice(slice);
    a
}

fn main() {
    let v = vec![0_u8, 1, 2, 3, 4, 5, 6, 7, 8];
    println!("res: {:X}", unpack_u32(clone_into_array(&v[1..5])));    
}

As you can see, there is still an assert and thus the possibility of runtime failure. The Rust compiler isn't able to know that v[1..5] is 4 elements long, because 1..5 is just syntactic sugar for Range which is just a type the compiler knows nothing special about.

查看更多
Animai°情兽
4楼-- · 2019-07-25 07:48

I had a similar problem, creating a fixed byte-array on stack corresponding to const length of other byte-array (which may change during development time)

A combination of compiler plugin and macro was the solution:

https://github.com/frehberg/rust-sizedbytes

查看更多
登录 后发表回答