通过type_id检查不安全类型转换的安全性如何?

时间:2020-10-12 10:55:39

标签: rust unsafe

我有一个名为Magnitude的枚举:

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

    /// Positive infinity
    PosInfinite,

    /// Negative infinity
    NegInfinite,
}

并且我想要实现From特性以能够从不同类型构造Magnitude:

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)
        }
    }
}

如果使用value检查f64的类型为type_id,将其转换为f64有多安全?

它通过了下面的测试,但我不知道它是否始终可以正常工作:

#[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());
}

1 个答案:

答案 0 :(得分:3)

一种更自然的实现所需方法的方法是针对不同类型编写不同的实现:

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)
        }
    }
}

但是,如果您已经有了具体的实现,则很难实现全面的实现

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

产生

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>`

虽然有an accepted RFC to support specialization in Rust,但尚未完全实现。就目前而言,我建议使用一种不太通用的方法:

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);

这没有提供全面的实现,但是很容易在支持的类型列表中添加其他类型。

相关问题