Using the const generics feature

I'm working on a struct which can decode fonts, and it's working fine, but in order to clean up the code, I'm trying to move the const generics which Rust has provided.

Here's the current code:

    // Read the next u32 from the Decoder's underlying reader.
    fn read_u32(&mut self) -> DecodeResult<u32> {
        let mut buffer = [0u8; 4];

        match self.reader.read_exact(&mut buffer) {
            Ok(_) => Ok(u32::from_be_bytes(buffer)),
            Err(_) => Err(DecodeErr::UnexpectedEOF),
        }
    }

    // Read the next u16 from the Decoder's underlying reader.
    fn read_u16(&mut self) -> DecodeResult<u16> {
        let mut buffer = [0u8; 2];

        match self.reader.read_exact(&mut buffer) {
            Ok(_) => Ok(u16::from_be_bytes(buffer)),
            Err(_) => Err(DecodeErr::UnexpectedEOF),
        }
    }

In order to refactor this code, I would like to have a function (something like):

    fn read<const N: usize>(&mut self) -> DecodeResult<u32> {
        let mut buffer = [0u8; N];

        match self.reader.read_exact(&mut buffer) {
            Ok(_) => Ok(u32::from_be_bytes(buffer)),
            Err(_) => Err(DecodeErr::UnexpectedEOF),
        }
    }

But this does NOT work since in the match arm, I use u32, but here it should be u32, u16, based on the value of the generic.

How to solve this the Rust way?

I suppose you could define your own trait like

use std::mem;

trait FromBytes<const N: usize> {
    fn from_be_bytes(bytes: [u8; N]) -> Self;
}

macro_rules! impl_from_bytes {
    ($($T:ident)*) => {
        $(
            impl FromBytes<{mem::size_of::<$T>()}> for $T {
                fn from_be_bytes(bytes: [u8; {mem::size_of::<$T>()}]) -> Self {
                    $T::from_be_bytes(bytes)
                }
            }
        )*
    }
}

impl_from_bytes! { // choose whichever you need :-)
    u8 u16 u32 u64 u128
    i8 i16 i32 i64 i128
    usize isize
    f32 f64
}

and then use it

fn read<const N: usize, T: FromBytes<N>>(&mut self) -> DecodeResult<T> {
    let mut buffer = [0u8; N];

    match self.reader.read_exact(&mut buffer) {
        Ok(_) => Ok(T::from_be_bytes(buffer)),
        Err(_) => Err(DecodeErr::UnexpectedEOF),
    }
}

Thanks for the solution, but I wanted to achieve less code, and here it seems that I need to write more code :slight_smile: Is this the only way to achieve what I'm trying to do?

The from_be_bytes functions are associated functions of u32 or u16, so it's not really possible to abstract over those, unless you define your own trait (or possibly find some in existing crates). You could compact your code by defining and using a macro, similar to how I used them in the trait implementation above.


macro_rules! read_uint_method {
    ($name:ident $uint:ident) => {
        pub fn $name(&mut self) -> DecodeResult<$uint> {
            let mut buffer = [0u8; std::mem::size_of::<$uint>()];

            match self.reader.read_exact(&mut buffer) {
                Ok(_) => Ok($uint::from_be_bytes(buffer)),
                Err(_) => Err(DecodeErr::UnexpectedEOF),
            }
        }
    }
}

impl .... {
    /// Read the next u32 from the Decoder's underlying reader.
    read_uint_method!(read_u32 u32);

    /// Read the next u16 from the Decoder's underlying reader.
    read_uint_method!(read_u16 u16);
}

Thanks for the clarification.