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?
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.