Amin Rayej
Amin Rayej

Reputation: 21

How safe is unsafe cast when it is checked by type_id?

I have an enum called Magnitude:

pub enum Magnitude<T> {
    /// A finite value
    Finite(T),

    /// Positive infinity
    PosInfinite,

    /// Negative infinity
    NegInfinite,
}

and I want to implement From trait to be able to construct Magnitude from different types:

impl<T: Any> From<T> for Magnitude<T> {
    fn from(value: T) -> Self {
        if value.type_id() == TypeId::of::<f64>() {
            unsafe {
                let value_f64 = (&value as *const T).cast::<f64>().as_ref().unwrap().clone();
                if  value_f64 == f64::INFINITY {
                    Magnitude::PosInfinite
                } else if value_f64 == f64::NEG_INFINITY {
                    Magnitude::NegInfinite
                } else {
                    Magnitude::Finite(value)
                }
            }
        } else if value.type_id() == TypeId::of::<f32>() {
            unsafe {
                let value_f32 = (&value as *const T).cast::<f32>().as_ref().unwrap().clone();
                if  value_f32 == f32::INFINITY {
                    Magnitude::PosInfinite
                } else if value_f32 == f32::NEG_INFINITY {
                    Magnitude::NegInfinite
                } else {
                    Magnitude::Finite(value)
                }
            }
        } else {
            Magnitude::Finite(value)
        }
    }
}

if value is checked to be of type f64 using type_id, how unsafe is it to cast it to f64?

it passes the test below but I don't know if it will work correctly all the time:

#[test]
fn f64_infinity() {
    let pos_inf: Magnitude<f64> = f64::INFINITY.into();
    let neg_inf: Magnitude<f64> = f64::NEG_INFINITY.into();

    assert!(pos_inf.is_pos_infinite());
    assert!(neg_inf.is_neg_infinite());
}

Upvotes: 2

Views: 1145

Answers (1)

Sven Marnach
Sven Marnach

Reputation: 602215

A more natural way of implementing what you want would be to write different implementations for different types:

impl From<f32> for Magnitude<f32> {
    fn from(value: f32) -> Self {
        if value == f32::INFINITY {
            Magnitude::PosInfinite
        } else if value == f32::NEG_INFINITY {
            Magnitude::NegInfinite
        } else {
            Magnitude::Finite(value)
        }
    }
}

However, it would be difficult to implement a blanket implementation if you already have concrete implementations

impl<T> From<T> for Magnitude<T> {
    fn from(value: T) -> Self {
        Magnitude::Finite(value)
    }
}

results in

conflicting implementations of trait `std::convert::From<f32>` for type `Magnitude<f32>`:
  --> src/lib.rs:22:1
   |
10 | impl From<f32> for Magnitude<f32> {
   | --------------------------------- first implementation here
...
22 | impl<T> From<T> for Magnitude<T> {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `Magnitude<f32>`

While there is an accepted RFC to support specialization in Rust, this has not yet been fully implemented. For the time being, I suggest a slightly less general approach:

macro_rules! magnitude_from_float_impl {
    ($t:ty) => {
        impl From<$t> for Magnitude<$t> {
            fn from(value: $t) -> Self {
                if value.is_infinite() {
                    if value.is_sign_positive() {
                        Magnitude::PosInfinite
                    } else {
                        Magnitude::NegInfinite
                    }
                } else {
                    Magnitude::Finite(value)
                }
            }
        }
    }
}

macro_rules! magnitude_from_impl {
    ($t:ty) => {
        impl From<$t> for Magnitude<$t> {
            fn from(value: $t) -> Self {
                Magnitude::Finite(value)
            }
        }
    }
}


magnitude_from_float_impl!(f32);
magnitude_from_float_impl!(f64);
magnitude_from_impl!(i64);
magnitude_from_impl!(i32);

This does not provide a blanket implementation, but it's easy to add further types to the list of supported types.

Upvotes: 3

Related Questions