overloadingrust

How can I approximate method overloading?


I am modeling an API where method overloading would be a good fit. My naïve attempt failed:

// fn attempt_1(_x: i32) {}
// fn attempt_1(_x: f32) {}
// Error: duplicate definition of value `attempt_1`

I then added an enum and worked through to:

enum IntOrFloat {
    Int(i32),
    Float(f32),
}

fn attempt_2(_x: IntOrFloat) {}

fn main() {
    let i: i32 = 1;
    let f: f32 = 3.0;

    // Can't pass the value directly
    // attempt_2(i);
    // attempt_2(f);
    // Error: mismatched types: expected enum `IntOrFloat`

    attempt_2(IntOrFloat::Int(i));
    attempt_2(IntOrFloat::Float(f));
    // Ugly that the caller has to explicitly wrap the parameter
}

Doing some quick searches, I've found some references that talk about overloading, and all of them seem to end in "we aren't going to allow this, but give traits a try". So I tried:

enum IntOrFloat {
    Int(i32),
    Float(f32),
}

trait IntOrFloatTrait {
    fn to_int_or_float(&self) -> IntOrFloat;
}

impl IntOrFloatTrait for i32 {
    fn to_int_or_float(&self) -> IntOrFloat {
        IntOrFloat::Int(*self)
    }
}

impl IntOrFloatTrait for f32 {
    fn to_int_or_float(&self) -> IntOrFloat {
        IntOrFloat::Float(*self)
    }
}

fn attempt_3(_x: &dyn IntOrFloatTrait) {}

fn main() {
    let i: i32 = 1;
    let f: f32 = 3.0;

    attempt_3(&i);
    attempt_3(&f);
    // Better, but the caller still has to explicitly take the reference
}

Is this the closest I can get to method overloading? Is there a cleaner way?


Solution

  • Yes, there is, and you almost got it already. Traits are the way to go, but you don't need trait objects, use generics:

    #[derive(Debug)]
    enum IntOrFloat {
        Int(i32),
        Float(f32),
    }
    
    trait IntOrFloatTrait {
        fn to_int_or_float(&self) -> IntOrFloat;
    }
    
    impl IntOrFloatTrait for i32 {
        fn to_int_or_float(&self) -> IntOrFloat {
            IntOrFloat::Int(*self)
        }
    }
    
    impl IntOrFloatTrait for f32 {
        fn to_int_or_float(&self) -> IntOrFloat {
            IntOrFloat::Float(*self)
        }
    }
    
    fn attempt_4<T: IntOrFloatTrait>(x: T) {
        let v = x.to_int_or_float();
        println!("{:?}", v);
    }
    
    fn main() {
        let i: i32 = 1;
        let f: f32 = 3.0;
    
        attempt_4(i);
        attempt_4(f);
    }
    

    See it working here.