Trait impl or generic with a calculated return type (std::make_unsigned)?

Is there a way to have a generic function that works across all signed integers which returns the equivalent size unsigned integer? I was going to use traits from num crate to determine input requirements. I have tried things like the following but '_' is not allowed in type sigs.

trait Foo {
    fn enc_foo(n: Self) -> _;
}

fn enc_foo(n: Signed) -> _ { }

In C++ I was using a template with std::enable_if, std::is_signed and std::make_unsinged for the return. Does anyone have any ideas or maybe I should just have enc_foo_i8, enc_foo_i16 etc?

You could always try something like this:

fn to_unsigned<TSigned: Copy, TUnsigned: Copy>(value: TSigned) -> TUnsigned {
    unsafe { *(&value as *const _ as *const TUnsigned) }
}

fn main() {
    let x: u32 = to_unsigned(1234i32);
    println!("{}", x);
}
assert_eq_ty!(<i8  as MakeUnsigned>::Unsigned,  u8);
assert_eq_ty!(<i32 as MakeUnsigned>::Unsigned, u32);
assert_eq_ty!(<u32 as MakeUnsigned>::Unsigned, u32);

pub
fn enc_foo<T> (i: T) -> <T as MakeUnsigned>::Unsigned
where
    T : IsSigned,
{
    T::make_unsigned(i)
}

<plug>
If you're okay with nightly, and a bit of Fn magic, you could use the overloadable crate like so:

#![feature(unboxed_closures, fn_traits)]
overloadable::overloadable! {
    pub make_unsigned as
    #[inline]
    fn(x: isize) -> usize { x as _ },
    #[inline] 
    fn(x: i8) -> u8 { x as _ },
    #[inline]
    fn(x: i16) -> u16 { x as _ },
    #[inline]
    fn(x: i32) -> i32 { x as _ },
    #[inline]
    fn(x: i64) -> u64 { x as _ },
    #[inline]
    fn(x: i128) -> u128 { x as _ }
}

assert_eq!(make_unsigned(0i8), 0u8);
assert_eq!(make_unsigned(0i16), 0u16);

</plug>

1 Like

I believe you can just use type-level functions a.k.a. associated types (playground):

trait ToUnsigned: Sized {
    type Unsigned;

    fn to_unsigned(self) -> Self::Unsigned;
}

macro_rules! impl_to_unsigned {
    ($($signed:ident => $unsigned:ident,)*) => {$(
        impl ToUnsigned for $signed {
            type Unsigned = $unsigned;

            fn to_unsigned(self) -> Self::Unsigned {
                self as $unsigned
            }
        }
    )*};
}

impl_to_unsigned! {
    i8 => u8,
    i16 => u16,
    i32 => u32,
    i64 => u64,
    i128 => u128,
    isize => usize,
}
2 Likes

H2CO3's answer is really similar to what I went with.

trait MakeSigned<T> {
    fn make_signed(self) -> T;
}

trait MakeUnsigned<T> {
    fn make_unsigned(self) -> T;
}

macro_rules! make_signed_impl {
    ($SIG:ty, $UNS:ty) => {
        impl MakeSigned<$SIG> for $UNS {
            fn make_signed(self) -> $SIG {
                self as $SIG
            }
        }

        impl MakeUnsigned<$UNS> for $SIG {
            fn make_unsigned(self) -> $UNS {
                self as $UNS
            }
        }
    }
}

make_signed_impl!(i8, u8);
make_signed_impl!(i16, u16);
make_signed_impl!(i32, u32);
make_signed_impl!(i64, u64);
make_signed_impl!(i128, u128);
make_signed_impl!(isize, usize);

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn make_signed() {
        let _a: u8 = 1i8.make_unsigned();
        let _b: u16 = 1i16.make_unsigned();
        let _c: u32 = 1i32.make_unsigned();
        let _d: u64 = 1i64.make_unsigned();
        let _e: u128 = 1i128.make_unsigned();
        let _f: usize = 1isize.make_unsigned();

        let _g: i8 = 1u8.make_signed();
        let _h: i16 = 1u16.make_signed();
        let _i: i32 = 1u32.make_signed();
        let _j: i64 = 1u64.make_signed();
        let _k: i128 = 1u128.make_signed();
        let _l: isize = 1usize.make_signed();
    }
}