Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for use num_traits::cast::NumCast #573

Open
MitchellNeill opened this issue Feb 9, 2023 · 3 comments
Open

Add support for use num_traits::cast::NumCast #573

MitchellNeill opened this issue Feb 9, 2023 · 3 comments

Comments

@MitchellNeill
Copy link

MitchellNeill commented Feb 9, 2023

Disclaimer I am pretty new to rust

I was wondering if support could be added for the num_traits::cast::NumCast

The trait is defined as part of this file: https://docs.rs/num-traits/latest/src/num_traits/cast.rs.html#721

I would like to implement it as I wish to use Decimal with the geo library: geo::Coord::::{x: 1., y:1.}
however it only accepts types which implement numcast.

I had a go at implementing how I think it should look , I think there are three high level options:

  1. Figure out the input type and call the appropriate conversion method (requires figuring out input type)
  2. Always convert input to an f64 and convert f64 to a Decimal (has some data loss when input is i128)
  3. Use some magic I am unaware of

I was wondering if I could get some pointers, or help implementing this.

In regards to figuring out the type, It looks like the standard approach here will not work, due to lifetimes, but I am not very good at using them, so there may be a work around. I have included code for this approach at the bottom of this pitiful cry for help. https://doc.rust-lang.org/std/any/index.html

I found a hacky looking, but powerful(?) approach here

which I modified and included below, but the method I use to check types is maybe dangerous:

use num_traits::cast::{FromPrimitive, NumCast, ToPrimitive};
use crate::prelude::Decimal;
use std::any::type_name;

// By directly calling `type_name` here we guarantee that the names
// remain "up to date".
const I8_NAME: &str = type_name::<i8>();
const I16_NAME: &str = type_name::<i16>();
const I32_NAME: &str = type_name::<i32>();
const I64_NAME: &str = type_name::<i64>();
const I128_NAME: &str = type_name::<i128>();

const ISIZE_NAME: &str = type_name::<isize>();

const F32_NAME: &str = type_name::<f32>();
const F64_NAME: &str = type_name::<f64>();

const U8_NAME: &str = type_name::<u8>();
const U16_NAME: &str = type_name::<u16>();
const U32_NAME: &str = type_name::<u32>();
const U64_NAME: &str = type_name::<u64>();
const U128_NAME: &str = type_name::<u128>();


impl NumCast for Decimal {
    fn from<T: ToPrimitive>(n: T) -> Option<Self> {
        match type_name::<T>() {

            I8_NAME => return Decimal::from_i8(n.to_i8().unwrap()),
            I16_NAME => return Decimal::from_i16(n.to_i16().unwrap()),
            I64_NAME => return Decimal::from_i64(n.to_i64().unwrap()),
            I128_NAME => return Decimal::from_i128(n.to_i128().unwrap()),

            F32_NAME => return Decimal::from_f32(n.to_f32().unwrap()),
            F64_NAME => return Decimal::from_f64(n.to_f64().unwrap()),

            U8_NAME => return Decimal::from_u8(n.to_u8().unwrap()),
            U16_NAME => return Decimal::from_u16(n.to_u16().unwrap()),
            U32_NAME => return Decimal::from_u32(n.to_u32().unwrap()),
            U64_NAME => return Decimal::from_u64(n.to_u64().unwrap()),
            U128_NAME => return Decimal::from_u128(n.to_u128().unwrap()),
            _ => None
        }
    }
}

Another attempt using ID but has problems with static life time

use num_traits::cast::{FromPrimitive, NumCast, ToPrimitive};
use crate::prelude::Decimal;
use std::any::{Any, TypeId};
use alloc::boxed::Box;

impl<'a> NumCast for Decimal {
    fn from<T: ToPrimitive>(n: T) -> Option<Self> {

        let i8 = TypeId::of::<i8>();
        let i16 = TypeId::of::<i16>();
        let i64 = TypeId::of::<i64>();
        let i128 = TypeId::of::<i128>();

        let f32 = TypeId::of::<f32>();
        let f64 = TypeId::of::<f64>();

        let u8 = TypeId::of::<u8>();
        let u16 = TypeId::of::<u16>();
        let u32 = TypeId::of::<u32>();
        let u64 = TypeId::of::<u64>();
        let u128 = TypeId::of::<u128>();
        let boxed: Box<dyn Any> = Box::new(n);
        let actual_id = (&*boxed).type_id();

        match actual_id {
            i8 => return Decimal::from_i8(n.to_i8().unwrap()),
            i16 => return Decimal::from_i16(n.to_i16().unwrap()),
            i64 => return Decimal::from_i64(n.to_i64().unwrap()),
            i128 => return Decimal::from_i128(n.to_i128().unwrap()),

            f32 => return Decimal::from_f32(n.to_f32().unwrap()),
            f64 => return Decimal::from_f64(n.to_f64().unwrap()),

            u8 => return Decimal::from_u8(n.to_u8().unwrap()),
            u16 => return Decimal::from_u16(n.to_u16().unwrap()),
            u32 => return Decimal::from_u32(n.to_u32().unwrap()),
            u64 => return Decimal::from_u64(n.to_u64().unwrap()),
            u128 => return Decimal::from_u128(n.to_u128().unwrap()),
            _ => None
        }
    }
}

edit: fixed copy paste error where I used u8 everywhere
edit: added second approach with issues around life times

@paupino
Copy link
Owner

paupino commented Feb 11, 2023

Hmm, I wonder if you can achieve this by calling out to try_from (of which we implement for each primitive type) followed by ok() to turn it into an option? The one tricky part may be the ToPrimitive constraint - though perhaps you could guard against that too.

Something like:

impl NumCast for Decimal where Decimal: TryFrom<T>,
{
    fn from<T: ToPrimitive>(n: T) -> Option<Self> {
        Decimal::try_from(n).ok()
    }
}

The one thing that this implementation wouldn't do however is return None if the type was not implemented for Decimal - it'd be a compiler error instead I guess. In that case, you'd need to create a wrapper for Decimal and implement TryFrom/NumCast for the wrapper.

I'm a little nervous about doing explicit type dispatch - I feel that there is probably something we could do to get the compiler doing that for us instead, though I could be wrong.

I'm happy to take a look at this, perhaps later next week? That said: more than happy to review a PR if you get something working.

@paupino
Copy link
Owner

paupino commented Feb 11, 2023

On second thoughts, this may be a bit trickier to implement because NumCast doesn't expose T at the trait level but instead at the function level... I'd need to have a deeper look into this.

@MitchellNeill
Copy link
Author

It certainly makes it a better trickier, especially since we can't be sure if the input type implements any sort of helpful methods. At the moment I am working around it so no rush. Once I get a bit better at Rust lifetime I might take a look at the second option I listed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants