Writing a generic number parsing function, but having an issue with the generic variable

I am writing a function for an assembler parser I am writing to convert number tokens into an actual unsigned value. I wanted to write a generic version so that I might use it in future for different sizes.

At issue is the portion where I take a signed number and convert it to the same bit representation in unsigned (2's complement).

My thinking goes that all the implementations that I support have such cast_unsigned function, yet it cannot find it. Is there a proper way to do this?

I only see the impl directly on the concrete type no specific trait I could utilize.

use num_traits::{Num, Signed, Unsigned};

trait HasSignedEquivalent: Unsigned {
    type SignedEquivalent: Signed;
}

impl HasSignedEquivalent for u8 {
    type SignedEquivalent = i8;
}
impl HasSignedEquivalent for u16 {
    type SignedEquivalent = i16;
}

impl HasSignedEquivalent for u32 {
    type SignedEquivalent = i32;
}

impl HasSignedEquivalent for u64 {
    type SignedEquivalent = i64;
}

impl HasSignedEquivalent for u128 {
    type SignedEquivalent = i128;
}

impl HasSignedEquivalent for usize {
    type SignedEquivalent = isize;
}

pub fn parse_number<U: Unsigned + HasSignedEquivalent>(text: &str) -> Option<U> {
    if text.starts_with("-") {
        let element = U::SignedEquivalent::from_str_radix(text, 10);

        return match element {                      // no method named `cast_unsigned` found for associated type `<U as HasSignedEquivalent>::SignedEquivalent` in the current scope
            Ok(elem) => Some(elem.cast_unsigned()), // <-- cast_unsigned 
            Err(_) => None,
        }
    } else if text.starts_with("$") {
        let element = U::from_str_radix(&text[1..], 16);

        return match element {
            Ok(elem) => Some(elem),
            Err(_) => None,
        }
    } else if text.starts_with("%") {
        let element = U::from_str_radix(&text[1..], 2);

        return match element {
            Ok(elem) => Some(elem),
            Err(_) => None,
        }
    } else {
        let element = U::from_str_radix(text, 10);

        return match element {
            Ok(elem) => Some(elem),
            Err(_) => None,
        }
    }
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0599]: no method named `cast_unsigned` found for associated type `<U as HasSignedEquivalent>::SignedEquivalent` in the current scope
  --> src/lib.rs:35:35
   |
35 |             Ok(elem) => Some(elem.cast_unsigned()), // <-- cast_unsigned 
   |                                   ^^^^^^^^^^^^^ method not found in `<U as HasSignedEquivalent>::SignedEquivalent`

For more information about this error, try `rustc --explain E0599`.
error: could not compile `playground` (lib) due to 1 previous error

EDIT: I have a version of this that compiles but it seems a little verbose, maybe there is a shorter form of it?

All the type system known is that <U as HasSignedEquivalent>::SignedEquivalent implements num_traits::Signed, which does not have a method cast_unsigned.
You could implement a trait for that provides this method for the respective types and require it instead of num_traits::Signed.

1 Like

You only need a few more trait bounds:

trait HasSignedEquivalent: Unsigned + FromBytes<Bytes: Sized> {
    type SignedEquivalent: Signed + ToBytes<Bytes = <Self as FromBytes>::Bytes>;
}
        return match element {
            Ok(elem) => Some(U::from_le_bytes(&elem.to_le_bytes())),
            Err(_) => None,
        }
2 Likes

I have taken your suggestion and used another suggestion with 'ne' instead of 'le'.

Make sure to use it on both calls then, otherwise you'll get endianness confusion on systems with different endianness.

1 Like

Oh yes, that wouldn't be fun.

1 Like