Search code examples
genericsrustcastingtraits

Creating a Rust function that can add i64, f64, or a mix of i64 and f64


I would like to write an add function that can take as input either i64 or f64 for the two parameters.

I was able to come up with the following:

use std::ops::Add;

fn add<T: Add<Output = T>>(a: T, b: T) -> T{
    a + b
}

fn main() {
    let a: i64 = 10;
    let b: i64 = 20;
    let c: f64 = 10.5;
    let d: f64 = 20.5;

    println!("{:?}", add(a, b)); // Outputs: 30
    println!("{:?}", add(c, d)); // Outputs: 31.0
}

Is it possible to modify this function so that it's possible to have :

  • one parameter being an i64
  • the other parameter being an f64

If either of the parameters is an f64 we do a cast, and return an f64.

type of a type of b returned type
i64 i64 i64
i64 f64 f64
f64 i64 f64
f64 f64 f64

The main function would have the following output:

fn main() {
    let a: i64 = 10;
    let b: i64 = 20;
    let c: f64 = 10.5;
    let d: f64 = 20.5;

    println!("{:?}", add(a, b)); // Outputs: 30    | i64 + i64 -> i64
    println!("{:?}", add(a, c)); // Outputs: 20.5  | i64 + f64 -> f64
    println!("{:?}", add(c, a)); // Outputs: 20.5  | f64 + i64 -> f64
    println!("{:?}", add(c, d)); // Outputs: 30.0  | f64 + f64 -> f64
}

Solution

  • You can do this using a trait. The trait can be identical to the built-in Add trait, but due to the orphan rule you can't use the Add trait itself. Here's an example implementation:

    pub trait MyAdd<RHS> {
        type Output;
    
        fn add(self, b: RHS) -> Self::Output;
    }
    
    impl MyAdd<i64> for i64 {
        type Output = i64;
    
        fn add(self, b: i64) -> Self::Output {
            self + b
        }
    }
    
    impl MyAdd<f64> for f64 {
        type Output = f64;
    
        fn add(self, b: f64) -> Self::Output {
            self + b
        }
    }
    
    impl MyAdd<i64> for f64 {
        type Output = f64;
    
        fn add(self, b: i64) -> Self::Output {
            self + b as f64
        }
    }
    
    impl MyAdd<f64> for i64 {
        type Output = f64;
    
        fn add(self, b: f64) -> Self::Output {
            self as f64 + b
        }
    }
    
    pub fn add<T, U>(a: T, b: U) -> T::Output
    where
        T: MyAdd<U>,
    {
        a.add(b)
    }
    

    Playground

    The orphan rule prevents you from implementing foreign traits on foreign types. The code above works around that by using a custom trait. Alternatively, you could use custom types, i.e. newtype wrappers around i64 and f64. Since the implementation matches the built-in implementation of addition, it would be enough to wrap one of the two types in a newtype wrapper.