Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

What is the most idiomatic way to convert a slice of u8 array into u32 using u32::from_le_bytes() in Rust?

I’m trying to parse a file format, for which I have a reference i.e at offset X read Y bytes to get Z information.

I want to use u32::from_le_bytes() to convert parts of the byte array into a u32. However, since slices are dynamically sized in Rust I need to call <[u8; 4]>::try_from() on the slice to ensure it is the right size, which feels very verbose.

In my case, the array is guaranteed to be of size 16 and the slice is 2..6. Using try_from() therefore feels like unnecessary work as statically, I can’t see how it would fail.

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

fn parse(data: &[u8;16]) -> Result<u32, &'static str> {
    let id_bytes = match <[u8; 4]>::try_from(&data[2..6]) {
        Ok(array) => array,
        Err(_e) => return Err("Failed to parse.")
    };
    
    let id : u32 = u32::from_le_bytes(id_bytes);
    
    Ok(id)
}

Alternatively, I could unwrap try_from() to make it a bit shorter, but again I feel like that is trying to hide an error that doesn’t exist.

fn parse2(data: &[u8;16]) -> Result<u32, &'static str> {
    let id_bytes = <[u8; 4]>::try_from(&data[2..6]).unwrap();
    let id : u32 = u32::from_le_bytes(id_bytes);
    Ok(id)
}

I could of course just get the values one by one, and the compiler doesn’t seem to complain about this, but obviously this isn’t sustainable for writing multiple u32, or even a u128.

fn parse3(data: &[u8;16]) -> Result<u32, &'static str> {
    let id : u32 = u32::from_le_bytes([data[2], data[3], data[4], data[5]]);
    Ok(id)
}

Lastly, I could also use copy_from_slice into an appropriately sized array:

fn parse4(data: &[u8;16]) -> Result<u32, &'static str> {
    let mut id_bytes : [u8;4] = [0x00;4];
    id_bytes.copy_from_slice(&data[2..6]);
    let id : u32 = u32::from_le_bytes(id_bytes);
    Ok(id)
}

Is there perhaps a better way I haven’t thought of, and is there any clear winner in terms of speed or idiomacy?

>Solution :

unwrap()ing is fine. Since an error is a bug, you should panic (and not return Err) on error; and since it is clear why the code cannot fail, you can use unwrap() and not expect().

However, if you want, you can use constant magic to index the array with a static guarantee you won’t fail. I don’t know if I would recommend this, but this is a viable option.

use std::ops::Index;

// We cannot use `const END: usize`, because of limited const generics capabilities in current Rust.
pub struct ConstRange<const START: usize, const LEN: usize>;

impl<T, const START: usize, const LEN: usize, const N: usize> Index<ConstRange<START, LEN>>
    for [T; N]
{
    type Output = [T; LEN];
    fn index(&self, _: ConstRange<START, LEN>) -> &Self::Output {
        struct Validate<const START: usize, const LEN: usize, const N: usize>;
        impl<const START: usize, const LEN: usize, const N: usize> Validate<START, LEN, N> {
            const VALID: () = if START + LEN > N {
                panic!("too big indices")
            };
        }
        // This makes invalid indexing fails at compile-time instead of at runtime.
        // Only works for `cargo check`, not `cargo build`, but hey, it works!
        _ = Validate::<START, LEN, N>::VALID;

        self[START..][..LEN].try_into().unwrap()
    }
}

/// Short for "const range", because `array[const_range!(1..3)]` is too long.
#[macro_export]
macro_rules! cr {
    ( $start:literal .. $end:literal ) => {
        $crate::ConstRange::<$start, { $end - $start }>
    };
}

Then use this like:

fn parse(data: &[u8; 16]) -> Result<u32, &'static str> {
    let id_bytes = data[cr!(2..6)];

    let id: u32 = u32::from_le_bytes(id_bytes);

    Ok(id)
}

You can expand it with other types of ranges or mutable indexing. This example is minimal.

Don’t worry about speed: the optimizer will optimize the check away anyway.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading