Method overloading: Rust philosophy


#1

hy is it that Rust does not allow method overloading? I assume it is a philosophical point - I would like to know what it is as it is one thing I really miss from C++

Example…

struct foo {
  a:usize,
}
impl foo {
  fn new() -> foo {
    foo{a:0}
  }
  fn new(b:usize) -> foo {
    foo{a:b}
  }
}

I cannot do the above (AFAIK)


#2

I’m thinking this is a side effect of the different mangling. When C++ compiles it mangles the names to mark the arguments, which means that the mangled names are actually different (despite being overloaded from your point of view as the same name). In Rust, if there’s really valid reason for needing it, I’ll do something ugly like (off the top of my head):

struct Foo {
    // whatever goes in here
}

impl Foo {
    fn new(arg0: usize, arg1: usize) -> Self {
        unimplemented!()
    }
}

macro_rules! create_foo {
    ($arg0:expr) => {
        $crate::Foo::new($arg0 as usize, 0usize)
    };

    ($arg0:expr, $arg1:expr) => {
        $crate::Foo::new(arg0 as usize, arg1 as usize)
    };
}

Those kinds of macros aside, the split naming ensures you know what you’re doing. Personally I rarely need that kind of macro these days because I realised I’m often doing something like this:

fn whatever_was_called(arg0: usize) -> Foo {
    let mut arg1: usize = 0;
    // conditional code to determine if arg1 changes
    Foo::new(arg0, arg1)
}

Where that’s not needed, or it’s just a generic/default type, I’ll use Default::default() (or MyType::default()).


As an aside, you may be looking for Default.

// This returns the default version of Foo, which is your `new()`.
impl Default for Foo {
    fn default() -> Self {
        Foo{
            // defaults go in here
        }
    }
}

I just realised that the reason I don’t even have ::new() in most of my code is because I have no reason/need for it due to how Default works.

Note: for known types you can use #[derive(Default)] above the structure definition to apply it automatically (this requires everything within it to have a Default implementation).


#3

AFAICT, the main technical reason for absence of traditional overloading is that it does not play that well with “full” type inference (how do we call type inference, when expressions after the current one are taken into account, in contrast with C++ auto and Scala’s type inference? Is that “traditional Hindley-Milner”? What’s auto then? Cast @glaebhoerl?). In particular this works in Rust:

fn foo(_: String) {}

fn main() {
    let x = Default::default();
    foo(x); // type of x inferred from this expression
}

The type of x is inferred based on the following call to foo. Because there’s no overloading, foo is unambiguous, and compiler can easily assign type String to x. With some work and penalties to ergonomics you can make this sort-of work with overloading, but that’s complicated. Although probably less complicated than overloading + implicit conversions combo?


#4

It mixes especially poorly with rust’s generics. C++ templates can get away with it because they have 2-phase lookup and monomorphization-time errors, but Rust wants to do better than that.

So if you want overloading in Rust, you need to do it via traits, so that it’s structured instead of ad-hoc. As an example, note that u64::from can take u8, u16, u32, or u64.

I call C++ auto, C# var, etc “deduction”, following C++'s terminology of “template argument deduction”.


#5

To complement the above comments, I’d like to note there is an idiom in Rust: non-empty type constructors are named with_<something>, such as Vec::with_capacity.

struct Foo {
    a: usize,
}

impl Foo {
    fn new() -> Foo {
        Foo { a: 0 }
    }

    fn with_b(b: usize) -> Foo {
        Foo { a: b }
    }
}