The As trait for converting primitive types

Problem

The as operator does not support working with generic types directly, which makes it impossible to directly convert a generic type (conditionally containing a primitive type) into another primitive type. Example of non-working code:

fn as<F, I>(var: F) -> I {
   var as f64
}
as::<i32, f64>(2_i32);

In the standard library, various traits such as From/Into, FromPrimitive/ToPrimitive, NumCast are implemented for primitive types for conversion. But none of them work quite as one would like:

  • FromPrimitive/ToPrimitive – requires specifying a particular method
  • NumCast – returns an Option
  • From/Into – often there is an overlap of implementations, requiring explicit annotation <_ as Into<_>>, also this causes the difficulty described by me in the problem with Into

In short, none of the implementations that I found reveal the full potential of the generality of generics (often this is a limitation of the generic to a specific conversion) and the as operator. That is, it does not give us the conversion of each primitive type into any other primitive type.

Solution

I propose the following implementation:

Summary
use paste::paste;

macro_rules! every_type_method {
    ($($t:ty),+) => {
        $(
            paste! {
                fn [<to_ $t>](&self) -> $t {
                    *self as $t
                }
            }
        )+
    };
}

macro_rules! impl_ToPrim_for_every_types {
    ($($t:ty),+) => {
        pub trait ToPrim: ToString {
            $(
                paste! {
                    fn [<to_ $t>](&self) -> $t;
                }
            )+
        }
        $(
            impl ToPrim for $t {
                every_type_method!(i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, f32, f64);
            }
        )+
        $(
            impl FromPrim for $t {
                paste! {
                    fn from<F: ToPrim>(value: F) -> $t {
                        value.[<to_ $t>]()
                    }
                }
            }
        )+
    }
}

pub trait FromPrim: ToPrim {
    fn from<F: ToPrim>(value: F) -> Self;
}

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

pub trait AsPrim: FromPrim {
    fn as_<T: FromPrim>(self) -> T;
}

impl<F: FromPrim> AsPrim for F {
    fn as_<I: FromPrim>(self) -> I {
        I::from::<F>(self)
    }
}

This implementation allows converting any type that implements the AsPrim conversion trait into any primitive type (in my case, only numerical).

This gives us simplicity and conciseness, as if we were using the as operator:

fn tmp<T: AsPrim>(num: T) -> usize {
   let num: f64 = num.as_();
   let num = num.as_::<i128>();
   let num = num + 0.5.as_::<i128>();
   num.as_()
}"

Note that the flexibility of as is largely considered a problem.

I think a trait like WrappingFrom for integer <-> integer conversions is far more likely than an As trait.

7 Likes