Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 539
Shepmaster Avatar asked Aug 12 '14 13:08

Shepmaster


People also ask

How can overloading method be resolved?

The process of compiler trying to resolve the method call from given overloaded method definitions is called overload resolution. If the compiler can not find the exact match it looks for the closest match by using upcasts only (downcasts are never done). As expected the output is 10.

What is method overloading explain with example?

In Java, two or more methods may have the same name if they differ in parameters (different number of parameters, different types of parameters, or both). These methods are called overloaded methods and this feature is called method overloading. For example: void func() { ... }

Can method overloading be done in different classes?

Usually, method overloading happens inside a single class, but a method can also be treated as overloaded in the subclass of that class — because the subclass inherits one version of the method from the parent class and then can have another overloaded version in its class definition.

How do the overloading methods can be ambiguous?

There are ambiguities while using variable arguments in Java. This happens because two methods can definitely be valid enough to be called by data values. Due to this, the compiler doesn't have the knowledge as to which method to call.


3 Answers

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.

like image 140
Vladimir Matveev Avatar answered Oct 08 '22 16:10

Vladimir Matveev


Here's another way that drops the enum. It's an iteration on Vladimir's answer.

trait Tr {
  fn go(&self) -> ();
}

impl Tr for i32 {
  fn go(&self) {
    println!("i32")
  }
}

impl Tr for f32 {
  fn go(&self) {
    println!("f32")
  }
}

fn attempt_1<T: Tr>(t: T) {
  t.go()
}

fn main() {
  attempt_1(1 as i32);
  attempt_1(1 as f32);
}
like image 20
joel Avatar answered Oct 08 '22 16:10

joel


Function Overloading is Possible!!! (well, sorta...)

This Rust Playground example has more a more detailed example, and shows usage of a struct variant, which may be better for documentation on the parameters.

For more serious flexible overloading where you want to have sets of any number of parameters of any sort of type, you can take advantage of the From<T> trait for conversion of a tuple to enum variants, and have a generic function that converts tuples passed into it to the enum type.

So code like this is possible:

fn main() {
    let f = Foo { };
    f.do_something(3.14);               // One f32.
    f.do_something((1, 2));             // Two i32's...
    f.do_something(("Yay!", 42, 3.14)); // A str, i32, and f64 !!
}

First, define the different sets of parameter combinations as an enum:

// The variants should consist of unambiguous sets of types.
enum FooParam {
    Bar(i32, i32),
    Baz(f32),
    Qux(&'static str, i32, f64),
}

Now, the conversion code; a macro can be written to do the tedious From<T> implementations, but here's what it could produce:

impl From<(i32, i32)> for FooParam {
    fn from(p: (i32, i32)) -> Self {
        FooParam::Bar(p.0, p.1)
    }
}
impl From<f32> for FooParam {
    fn from(p: f32) -> Self {
        FooParam::Baz(p)
    }
}
impl From<(&'static str, i32, f64)> for FooParam {
    fn from(p: (&'static str, i32, f64)) -> Self {
        FooParam::Qux(p.0, p.1, p.2)
    }
}

And then finally, implement the struct with generic method:

struct Foo {}

impl Foo {
    fn do_something<T: Into<FooParam>>(&self, t: T) {
        use FooParam::*;
        let fp = t.into();
        match fp {
            Bar(a, b)    => print!("Bar: {:?}, {:?}\n", a, b),
            Baz(a)       => print!("Baz: {:?}\n", a),
            Qux(a, b, c) => {
                print!("Qux: {:?}, {:?}, {:?}\n", a, b, c)
            }
        }
    }
}

Note: The trait bound on T needs to be specified.

Also, the variants need to be composed of combinations of types that the compiler wouldn't find ambiguous - which is an expectation for overloaded methods in other languages as well (Java/C++).

This approach has possibilities... it would be awesome if there's a decorator available - or one were written that did the From<T> implementations automatically when applied to an enum. Something like this:

// THIS DOESN'T EXIST - so don't expect the following to work.
// This is just an example of a macro that could be written to
// help in using the above approach to function overloading.

#[derive(ParameterOverloads)]
enum FooParam {
    Bar(i32, i32),
    Baz(f32),
    Qux(&'static str, i32, f64),
}

// If this were written, it could eliminate the tedious
// implementations of From<...>.
like image 5
Todd Avatar answered Oct 08 '22 15:10

Todd