How to get function overloading behaviour in Rust?

I have this function that will adds 2 u8 numbers:

fn main() {
    fn add(num_0: u8, num_1: u8) -> u8 {
        num_0 + num_1
    }

    println!("{}", add(1, 2));
}

Now i want to have the same function but with 2 different versions

  1. One that takes 2 u8 numbers, add them and return a u8
  2. Another one that takes 2 u32 numbers, add them and return u32

In Java i can do function overloading like this:

fn main() {
    fn add(num_0: u8, num_1: u8) -> u8 {
        num_0 + num_1
    }

    fn add(num_0: u32, num_1: u32) -> u8 {
        num_0 + num_1
    }

    println!("{}", add(1, 2));
    println!("{}", add(122, 234));
}

However this gives me an error
error[E0428]: the name add is defined multiple times
so i try using a generic type:

fn main() {
    fn add<UInt>(num_0: UInt, num_1: UInt) -> UInt {
        num_0 + num_1
    }

    println!("{}", add(12392, 255));
}

Still getting an error
error[E0369]: cannot add UInt to UInt

How can i solve this problem ?
Can i solve it using traits ?

Addition is not defined for every type. For a type parameter, how should the compiler know that it makes sense for your type to be added? You have to add trait bounds that describe what operations are defined on each generic type parameter. For addition, that's the std::ops::Add trait.

use std::ops::Add;

fn add<UInt>(num_0: UInt, num_1: UInt) -> UInt
where
    UInt: Add<UInt, Output = UInt>
{
    num_0 + num_1
}

fn main() {
    println!("{}", add(12392_u32, 255));
}
2 Likes

Yes, you can do it with traits.

fn add<UInt: Add<UInt, Output = UInt>>(num_0: UInt, num_1: UInt) -> UInt {
    num_0 + num_1
}

In general,

  • There is no argument-based overloading except generics
  • With generics you have to specify any capability you make use of with a trait bound
1 Like

If you take a closer look at the full compiler output, a solution is suggested:

help: consider restricting type parameter `UInt`
  |
3 |     fn add<UInt: std::ops::Add<Output = UInt>>(num_0: UInt, num_1: UInt) -> UInt {
  |                ++++++++++++++++++++++++++++++

you can modify your code following this suggestion, (i.e., adding a trait bound, as already mentioned above, but using a different syntax). The following code run just fine, as suggested by the compiler:

fn main() {
    fn add<UInt: std::ops::Add<Output = UInt>>(num_0: UInt, num_1: UInt) -> UInt {
        num_0 + num_1
    }

    println!("{}", add(12392, 255));
}
1 Like

Thank you all for help. @pzometa I know that the solution is in the compiler output, but i want to restrict the function to only work for u8 and u32.

So what does this line mean UInt: Add<UInt, Output = UInt> ?

And how to implement it myself without using the Add type. I forgot to mention that I want to restrict the add function to only accept u8s and u32s ?

I saw something similar to the example i posted here in a book and i am trying to understand why do traits exist, they seem confusing

You can write your own trait that does whatever you want for only the specific types you want.

That's why i wrote this post, cuz i tried doing it and it didn't work for me. Can you show me an example please ?

You can do that without having to abstain from the better ergonomics offered by Add (namely that you can use the + operator) by making Add a supertrait of your trait:

use std::ops::Add;

trait MyTrait: Add<Self, Output = Self> + Sized {}

impl MyTrait for u8 {}
impl MyTrait for u32 {}

fn add<UInt: MyTrait>(num_0: UInt, num_1: UInt) -> UInt
{
    num_0 + num_1
}

fn main() {
    assert_eq!(add(1_u8, 5), 6);
    assert_eq!(add(1_u32, 5), 6);
    
    // error, because u64 does not implement MyTrait
    //assert_eq!(add(1_u64, 5), 6);
}

Playground.

If you want keep the whole functionality inside your trait, this could be a solution:

trait MyTrait {
    fn add(&self, other: Self) -> Self;
}

impl MyTrait for u8 {
    fn add(&self, other: Self) -> Self {
        self + other
    }
}

impl MyTrait for u32 {
    fn add(&self, other: Self) -> Self {
        self + other
    }
}

fn add<UInt: MyTrait>(num_0: UInt, num_1: UInt) -> UInt
{
    num_0.add(num_1)
}

fn main() {
    assert_eq!(add(1_u8, 5), 6);
    assert_eq!(add(1_u32, 5), 6);
    
    // error, because u64 does not implement MyTrait
    //assert_eq!(add(1_u64, 5), 6);
}

Playground.

5 Likes

It means that whatever type gets subsituted for the type variable UInt, it must have an Add impl where both the RHS and the result have the same type as itself. This can be deduced from the documentation of the relevant trait.

Did you read the relevant chapter in the Book?

1 Like

@H2CO3 @pzometa @scottmcm @jofas @quinedot Thanks a lot for all your help.
Also thanks to ChatGPT, it helped me a lot.

off-topic: I've been using ChatGPT for different tasks (including code generation of Python code). I am genuinely interested in knowing more about how did ChatGPT helped you a lot?

Something like this

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.