Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I create a function with two generic parameters, which can be whatever kind of number, and perform simple arithmetic with them?

Tags:

rust

I am trying to learn how to create a function in Rust that will add up two numbers, regardless of their types (two integers, two floats, a float and an integer or an integer and a float). I am trying to achieve this using generic parameters in my function signature. Below is the sample Rust code:

use num_traits::Num;

fn main() {
    println!("{}", sum_two_numbers(5, 6));
}

fn sum_two_numbers<T: Num, U: Num>(num1: T, num2: U) -> U{
    num1 + num2
}

The above does not compile. The error I get is:

error[E0308]: mismatched types
  --> src\main.rs:13:12
   |
12 | fn sum_two_numbers<T: Num, U: Num>(num1: T, num2: U) -> U{
   |                    -       - found type parameter
   |                    |
   |                    expected type parameter
13 |     num1 + num2
   |     ----   ^^^^ expected type parameter `T`, found type parameter `U`
   |     |
   |     expected because this is `T`
   |
   = note: expected type parameter `T`
              found type parameter `U`
   = note: a type parameter was expected, but a different one was found; you might be missing a type parameter 
or trait bound
   = note: for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters 

error[E0308]: mismatched types
  --> src\main.rs:13:5
   |
12 | fn sum_two_numbers<T: Num, U: Num>(num1: T, num2: U) -> U{
   |                    -       - expected type parameter    - expected `U` because of return type
   |                    |
   |                    found type parameter
13 |     num1 + num2
   |     ^^^^^^^^^^^ expected type parameter `U`, found type parameter `T`
   |
   = note: expected type parameter `U`
              found type parameter `T`
   = note: a type parameter was expected, but a different one was found; you might be missing a type parameter 
or trait bound
   = note: for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters 

For more information about this error, try `rustc --explain E0308`.
error: could not compile `generics-for-stackoverflow-question` (bin "generics-for-stackoverflow-question") due 
to 2 previous errors

I am aware that i32 types can be cast to f64 like so:

5 as f64;

I was hoping the compiler would be able to figure out that I was trying to add two different number types and help me cast them. Clearly it does not work.

Please note, that the Num trait comes from an external crate that I added to my project (the crate is num_traits).

I have previously written the function in a way that compiles as below:

use num_traits::Num;

fn main() {
    println!("{}", sum_two_numbers(5, 6));
}

fn sum_two_numbers<T: Num>(num1: T, num2: T) -> T{
    num1 + num2
}

This would take care of adding an integer to an integer or a float to float, but not a float to a integer or vice-versa.

like image 307
DammyD Avatar asked Feb 04 '26 15:02

DammyD


1 Answers

Additions between arbitrary types are supported in Rust for those types that implement those combination of types explicitly. However for numbers there are only implementations for additions between numbers of the same type. So we'll have to make sure both numbers are of the same type. We can do the conversion automatically for the user, given that the conversion from one type to the other is defined. The From trait is implemented for types that support conversion from another type to that type.

So how do we get the compiler to allow you to do that conversion? Say we assume we first want to convert the type of the first number (T) to the type of the second number (U) before doing the addition, we need to require type U to support automatic conversion from type T. We do that by requiring U to implement not only Num but also From<T>. After that, it will allow us to access that additional functionality, and we can convert num1 to U. Then the addition will work out just fine.

fn sum_two_numbers<T: Num, U: Num + From<T>>(num1: T, num2: U) -> U{
    U::from(num1) + num2
}

Please note that generally only conversion that does not risk losing precision is defined for number types, so you can automatically convert from e.g. u8 to u16 but not the other way around. So this works fine:

// 5u8 is first converted to u16, and then added to 6u16
println!("{}", sum_two_numbers(5u8, 6u16));

But this does not compile:

println!("{}", sum_two_numbers(5u16, 6u8));
  |                    ---------------       ^^^ the trait `From<u16>` is not implemented for `u8`
  |                    |
  |                    required by a bound introduced by this call

If you want to try the operation anyway, you can use the conditional conversion support with the TryFrom trait instead. It tries to do the conversion, but will give a runtime error if it fails instead:

fn sum_two_numbers2<T: Num, U: Num + TryFrom<T>>(num1: T, num2: U) -> Result<U, U::Error> {
    Ok(U::try_from(num1)? + num2)
}

The question mark operator makes the function return the error in case the conversion fails. Otherwise, the result from the addition is wrapped in an Ok() result, indicating success.

This previous examples now both compile & run fine (I use the question mark operator here as well to make the main function return the error in case the conversion fails):

fn main() -> Result<(), Box<dyn Error>> {
    println!("{}", sum_two_numbers2(5u16, 6u8)?);
    println!("{}", sum_two_numbers2(5u8, 6u16)?);
    Ok(())
}
// prints "11" twice

In case the number is too big, it will bail:

fn main() -> Result<(), Box<dyn Error>> {
    println!("{}", sum_two_numbers2(1000u16, 6u8)?);
    Ok(())
}
// prints: Error: TryFromIntError(())

Likewise, your example with integer to float works with both my implementations when the float is the last argument :

println!("{}", sum_two_numbers(1000u16, 6.3f32));
println!("{}", sum_two_numbers2(1000u16, 6.3f32)?);
// prints: 1006.3 twice

The other way around will fail however because the result type will always be that of the second argument, as suggested by your attempts in the question, and even conditional conversion is not supported from float to integer. You could of course implement your own trait MyFrom for all the number types you need to convert between and support more options with whatever side effects those imply.

Now while adding error handling we might be tempted to also support checking for overflow and returning an error in that case too (like you can do 200u8.checked_add(200u8) and get None to indicate overflow), but alas the num_traits crate does not seem to support that.

like image 191
Jonas Berlin Avatar answered Feb 06 '26 05:02

Jonas Berlin