Search code examples
genericsrusttypestraits

Trying to make bounded floating point types in rust, problem with From and Into


Here is the code, I'm trying to make bounded floating point numbers. I do this with a Bounds trait, that must be implemented for the generic type B in the Bounded number struct. I would like to be able to do .into() on the Bounded type and get the contained value.

use core::marker::PhantomData;

trait Bounds<T> {
    const MIN: T;
    const MAX: T;
}

struct Bounded<T, B> {
    val: T,
    _m: PhantomData<B>,
}

impl<T: PartialOrd, B: Bounds<T>> Bounded<T, B> {
    pub fn new(val: T) -> Option<Self> {
        if B::MIN <= val && val <= B::MAX {
            Some(Self {
                val,
                _m: Default::default(),
            })
        } else {
            None
        }
    }

    pub fn new_sat(val: T) -> Self {
        Self {
            val: if val < B::MIN {
                B::MIN
            } else if val > B::MAX {
                B::MAX
            } else {
                val
            },
            _m: Default::default(),
        }
    }
}

impl<T: PartialOrd, B: Bounds<T>> From<T> for Bounded<T, B> {
    fn from(value: T) -> Self {
        Self::new_sat(value)
    }
}

impl<T, B> Into<T> for Bounded<T, B> {
    fn into(self) -> T {
        self.val
    }
}

struct ZeroToTenf32;

impl Bounds<f32> for ZeroToTenf32 {
    const MIN: f32 = 0.0;
    const MAX: f32 = 10.0;
}

fn main() {
    let a: Bounded<_, ZeroToTenf32> = Bounded::new_sat(5.0);
    let b: f32 = a.into();
    println!("Hello, world!");
}

This is what I'm getting, I don't understand why the blanket implementation is in conflict with mine:

cargo c
    Checking weird_types v0.1.0 (/home/tommaso/projects/rust/desktop/weird_types)
error[E0119]: conflicting implementations of trait `Into<_>` for type `Bounded<_, _>`
  --> src/main.rs:45:1
   |
45 | impl<T, B> Into<T> for Bounded<T, B> {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: conflicting implementation in crate `core`:
           - impl<T, U> Into<U> for T
             where U: From<T>;

For more information about this error, try `rustc --explain E0119`.
error: could not compile `weird_types` (bin "weird_types") due to 1 previous error

Solution

  • Sadly, at the time of writing, this does not seem to be possible.

    You usually do not implement Into, because there is a blanket implementation in the standard library already:

    impl<T, U> Into<U> for T where U: From<T>
    

    So usually you should implement From instead.

    That said, in your case you cannot do that, because for an impl to work, you need either the trait or the type to be local to your crate, to avoid (present or future) impl collisions. That is problematic in your case, as T is of course not local, and the Rust compiler sees From<Bounded<T, B>> also as non-local.

    There are a couple of alternative routes I would recommend for your code, none of which implement .into(), though.

    • Implement a custom .into_inner() function for your trait
    • Make your trait a smart pointer by implementing Deref
    use core::marker::PhantomData;
    
    trait Bounds<T> {
        const MIN: T;
        const MAX: T;
    }
    
    struct Bounded<T, B> {
        val: T,
        _m: PhantomData<B>,
    }
    
    impl<T: PartialOrd, B: Bounds<T>> Bounded<T, B> {
        pub fn new(val: T) -> Option<Self> {
            if B::MIN <= val && val <= B::MAX {
                Some(Self {
                    val,
                    _m: Default::default(),
                })
            } else {
                None
            }
        }
    
        pub fn new_sat(val: T) -> Self {
            Self {
                val: if val < B::MIN {
                    B::MIN
                } else if val > B::MAX {
                    B::MAX
                } else {
                    val
                },
                _m: Default::default(),
            }
        }
    
        pub fn into_inner(self) -> T {
            self.val
        }
    }
    
    impl<T: PartialOrd, B: Bounds<T>> From<T> for Bounded<T, B> {
        fn from(value: T) -> Self {
            Self::new_sat(value)
        }
    }
    
    impl<T, B> std::ops::Deref for Bounded<T, B> {
        type Target = T;
    
        fn deref(&self) -> &Self::Target {
            &self.val
        }
    }
    
    struct ZeroToTenf32;
    
    impl Bounds<f32> for ZeroToTenf32 {
        const MIN: f32 = 0.0;
        const MAX: f32 = 10.0;
    }
    
    fn main() {
        let a: Bounded<_, ZeroToTenf32> = Bounded::new_sat(5.0);
        let b: &f32 = &a;
        let c: f32 = a.into_inner();
    }