genericsrusttypes

Can a generic function take a custom type as an argument in Rust?


So I did manage to make a generic function where I can use the default types:

fn arithmetic_operation<T, F>(a: T, b: T, operation: F) -> T
where 
    T: Copy,
    F: Fn(T, T) -> T,
{
    operation(a, b)
}

fn main() {
    // f64
    let result_f64 = arithmetic_operation(2.0, 3.0 , |x, y| x+y);
    println!("Result  f64: {:?}", result_f64);
    // Result  f64: 5.0

    // i128
    let result_i128 = arithmetic_operation(2i128, 3i128 , |x, y| x+y);
    println!("Result  i128: {:?}", result_i128);
    // Result  i128: 5
}

playground

I would like to create some custom types (like money, km, ...) and still use generic functions. But I can't figure out how to write a function signature for custom types.

If I write a custom type defined with an enum:

#[derive(Debug, PartialEq, Copy, Clone)]
pub enum ResType {
    Int(i128),
    Float(f64),
}

impl ResType {
    fn get_i128(self) -> i128 {
        match self {
            ResType::Int(val)  => val,
            ResType::Float(val) => val as i128
        }
    }

    fn get_f64(self) -> f64 {
        match self {
            ResType::Int(val)  => val as f64,
            ResType::Float(val) => val
        }
    }

    fn is_float(&self) -> bool {
        matches!(self, ResType::Float(_) )
    }
}

fn arithmetic_operation<T, F>(a: ResType, b: ResType, func: F) -> ResType 
where 
    T: Copy,
    F: Fn(T, T) -> T,
{
    if a.is_float() || b.is_float() {
        let res = func(a.get_f64(), b.get_f64());
        return ResType::Float(res);
    }

    let res = func(a.get_i128(), b.get_i128());
    ResType::Int(res)
}


fn main() {
    let a = ResType::Int(42);
    let b = ResType::Int(13);

    println!("Result ResType: {:?}", arithmetic_operation(a, b, |x,y| x+y));
}

playground

I have some errors explaining that a T is required, but an f64 is given.

note: expected type parameter `T`, found `f64`
  --> src/main.rs:33:24
   |
33 |         let res = func(a.get_f64(), b.get_f64());
   |                        ^^^^^^^^^^^
   = note: expected type parameter `T`
                        found type `f64`

How can I make this type of generic function work with a custom type ? Or is this a wrong approach altogether to create new types ?


Solution

  • You cannot add two factors of type ResType. Instead, you have to consider how to link the variants of ResType. Better is to implement the trait From for f64 and i128 instead of the homebrewed functions get_f64() and get_i128().
    There are traits for the most arithmetic functions in Rust, e.g. Add that you could implement for your ResType.
    Here is a working version of your program:

    #[derive(Debug, PartialEq, Copy, Clone)]
    pub enum ResType {
        Int(i128),
        Float(f64),
    }
    
    impl From<ResType> for i128 {
        fn from(rt: ResType) -> i128 {
            match rt {
                ResType::Int(val) => val,
                ResType::Float(val) => val as i128, // loss of accuracy!
            }
        }
    }
    
    impl From<ResType> for f64 {
        fn from(rt: ResType) -> f64 {
            match rt {
                ResType::Int(val) => val as f64,
                ResType::Float(val) => val,
            }
        }
    }
    
    fn arithmetic_operation(a: ResType, b: ResType, func: fn(ResType, ResType) -> ResType) -> ResType {
        (func)(a, b)
    }
    
    fn main() {
        let a = ResType::Int(42);
        let b = ResType::Int(13);
    
        println!(
            "Result ResType: {:?}",
            arithmetic_operation(a, b, |a: ResType, b: ResType| {
                match a {
                    ResType::Int(i) => {
                        // loss of accuracy if b is Float!
                        ResType::Int(i + i128::from(b))
                    }
                    ResType::Float(f) => ResType::Float(f + f64::from(b)),
                }
            })
        );
    }
    

    Playground