1

Is there a better way to implement a common conversion function over primitive number types? I've been struggling to create a more generic version of a trait, that converts a LE byte stream to specific type, and then returns an option float64. In this routine, if a number of specific type is max positive or max (positive-1), it is invalid. It seems this should be possible with a function that takes generics and uses a where clause, but I haven't been able to find a working solution.


struct U16([u8;2]);
struct U32([u8;4]);
struct I16([u8;2]);
struct I32([u8;4]);

trait CanNumber {
    fn to_number(&self) -> Option<f64>;
}
impl CanNumber for U16 {
    fn to_number(&self) -> Option<f64> {
        type nt = u16;
        let num: nt = nt::from_le_bytes(self.0);
        if (num == nt::MAX) | (num == (nt::MAX-1)) {
            None
        }
        else {
            Some(num as f64)
        }
    }
}
impl CanNumber for I16 {
    fn to_number(&self) -> Option<f64> {
        type nt = i16;
        let num: nt = nt::from_le_bytes(self.0);
        if (num == nt::MAX) | (num == (nt::MAX-1)) {
            None
        }
        else {
            Some(num as f64)
        }
    }
}

fn main() {
    let test0: f64 = U16([1,2]).to_number().unwrap();
    let test1: f64 = I16([1,2]).to_number().unwrap();
    println!("test!");
}

1 Answer 1

1

This might be a good candidate for a macro. You can handle this case by writing a trait and then using a macro to perform the implementation across several types. For example:

trait FromFooLittleEndian {
    type Bytes;
    type Number;

    fn from_foo_little_endian(bytes: Self::Bytes) -> Option<Self::Number>;
}

macro_rules! impl_from_little_endian {
    ( $( $ty:path, $bytes:literal );* $( ; )? ) => {
        $(
            impl FromFooLittleEndian for $ty {
                type Bytes = [u8; $bytes];
                type Number = $ty;

                fn from_foo_little_endian(bytes: Self::Bytes) -> Option<Self::Number> {
                    let num = <$ty>::from_le_bytes(bytes);
                    (num < <$ty>::MAX - 1).then_some(num)
                }
            }
        )*
    };
}

impl_from_little_endian!(
    u16, 2; i16, 2;
    u32, 4; i32, 4;
    u64, 8; i64, 8;
);

#[test]
fn foo_little_endian() {
    assert_eq!(Some(42), u16::from_foo_little_endian(42u16.to_le_bytes()));
    assert_eq!(None, u16::from_foo_little_endian(u16::MAX.to_le_bytes()));
    assert_eq!(
        None,
        u16::from_foo_little_endian((u16::MAX - 1).to_le_bytes())
    );
    assert_eq!(
        Some(u16::MAX - 2),
        u16::from_foo_little_endian((u16::MAX - 2).to_le_bytes())
    );
}

(Playground)

Note that the term foo here is a placeholder for an application-specific name for this kind of byte stream and I would suggest replacing it with an appropriate name in your application.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.