0

I'm writing a library that deals with numerical attributes. For this I have created a trait for all the number primitives:

trait Sealed {}

#[allow(private_bounds, reason = "To seal trait Number")]
pub trait Number: Sealed
    + Sized + Clone + Copy
    + Add<Output = Self>
    + Sub<Output = Self>
    + Mul<Output = Self>
    + Div<Output = Self>
{}

Assume that this trait is implemented for all numerical types (u8, u16, etc...). It's also sealed so that no other type can implement it.

Naturally, you can convert numerical primitives from and to each other using the as keyword, it's a simple cast. However, if all we know about a type T is that it implements Number, this cast no longer works.

To address this, I have created another trait that allows conversion between Number instances:

pub trait ConvertNumber<T>: Number
where
    T: Number
{
    fn convert(self) -> T;
}

Again, assume that this trait is implemented for every pair of numerical primitives.

This now works, but every time I need conversion between different types that implement Number, I have to add an extra constraint to ConvertNumber, which can lead to the API leaking implementation details.

My question is, can I make the compiler know that every T: Number is convertible to any other type that implements Number without extra constraints?

9
  • "assume that this trait is implemented for every pair of numerical primitives" - the compiler can't assume that unless there was a blanket implementation, which I'm guessing isn't possible here. The compiler doesn't consider sealed traits / private traits for coherency. Commented Apr 7 at 18:19
  • I understand that. I'm not saying I require usage of this ConvertNumber trait, I'm just looking for any solution that works. Commented Apr 7 at 18:27
  • 1
    You can add a trait bound ConvertNumber<u8> + ConvertNumber<u16> + .. to Number (Number is sealed, after all, so the set of instances is finite). Otherwise you can use Into<VariantNumber> where enum VariantNumber { U8(u8), U16(u16), ... } Commented Apr 7 at 18:51
  • When you say "make the compiler know [..] is convertible" - what exactly do you mean? What do you want the compiler to do (or allow you to do) with this "knowledge"? Commented Apr 7 at 18:51
  • 1
    Just for completeness.. are you aware of the num crate ? Commented Apr 7 at 19:00

1 Answer 1

0

I was scratching my head for a while, but I found a complete solution to my own problem.

This solution requires the usage of two more traits, FromPrimitive and ToNumber.

pub trait ToNumber: Sealed {
    fn convert<T: Number>(self) -> T;
}

pub trait FromPrimitive: Sealed {
    fn from_u8(n: u8) -> Self;
    fn from_u16(n: u16) -> Self;
    fn from_u32(n: u32) -> Self;
    fn from_u64(n: u64) -> Self;
    fn from_u128(n: u128) -> Self;
    fn from_usize(n: usize) -> Self;

    fn from_i8(n: i8) -> Self;
    fn from_i16(n: i16) -> Self;
    fn from_i32(n: i32) -> Self;
    fn from_i64(n: i64) -> Self;
    fn from_i128(n: i128) -> Self;
    fn from_isize(n: isize) -> Self;

    fn from_f32(n: f32) -> Self;
    fn from_f64(n: f64) -> Self;
}

I just needed to implement these new traits for all primitives, then constrain trait Number even further:

pub trait Number: Sealed
    + FromPrimitive
    + ToNumber
    + Sized + Clone + Copy
    + Add<Output = Self>
    + Sub<Output = Self>
    + Mul<Output = Self>
    + Div<Output = Self>
{}

With this, I can freely convert between generic instances of Number without requiring any additional constraints on the callsite.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.