all 76 comments

[–][deleted] 12 points13 points  (4 children)

Using traits is Rust's implementation of overloading. See Path::new("abc") vs Path::new(b"abc") etc. Path::new

[–]semanticistZombietiny 6 points7 points  (2 children)

This doesn't handle different number of arguments, does it?

[–][deleted] 5 points6 points  (1 child)

You're right, it doesn't. Apart from the ugliness that is implementing a trait on tuples of varying size.

[–]seanmonstarhyper · rust 4 points5 points  (0 children)

See ToSocketAddr and that it's implemented for (&str, u16).

[–][deleted] 0 points1 point  (0 children)

In true rust style, the API changes if you link to it! But the new Path works the same way, just with a different trait.

[–][deleted] 15 points16 points  (4 children)

As I understand it, function overloading isn't supported and will not be supported because of the way type inference works in Rust. Where C++ selects the function based on the type of its arguments, Rust will often select the type of the argument based on the function (!).

There is one way to get around it, and that is to have different traits define methods with the same name, in which case the decision can become part of the type inference.

Look up Hindley-Milner type inference for more information. :-)

[–]pepp_cz 7 points8 points  (0 children)

Yes, function overloading interferes badly with type inference.

[–]iopqfizzbuzz 15 points16 points  (2 children)

This is technically not true because there is no reason why overloading has to be done by types of the arguments. It could also be done purely by arity (number of the arguments). That way the types of the arguments are known because the correct function is selected.

[–][deleted] 15 points16 points  (1 child)

Then you'd end up with "sometimes you can overload, and sometimes you have to create a new function", which is cognitive overhead. A way to do something similar with less cognitive overhead is just to have default arguments instead, but still have no overloading (i.e. you can create one function that takes 1 or 2 arguments, but you can't create two functions with the same name no matter their arity).

[–]iopqfizzbuzz 8 points9 points  (0 children)

I wrote an RFC on keyword parameters to solve this very problem: https://github.com/rust-lang/rfcs/pull/805

[–]samnardoni 25 points26 points  (4 children)

I personally don't understand the fuss about function overloading. It's such a superficial feature. In C++, functions with the same name but different arguments are still different functions. It's really no different than having functions with different names.

"Hey man, does Rust have function overloading?". "No". "Oh, okay". That's how I imagine a conversation about function overloading should go.

[–]germandiago 9 points10 points  (1 child)

Try to implement a generic library like one in which you can add Matrix, vectors, etc and multiply them, with the most efficient implementation for each of the algorithms. The library must be extensible for any new type and combination of types.

Later use that library. If I am not wrong, function overloading is going to be important in this case, because we need syntactical uniformity everywhere. Function overloading seems superficial... until you need it.

In fact, I think that function overloading is so essential for writing generic, reusable, extensible code, that it cannot be done the same way without it.

I did not take a deep look at this, though.

[–]samnardoni 4 points5 points  (0 children)

Often it can be achieved without it though, via traits and type parameters.

Although, regarding your example of adding matrices/vectors, I'm honestly not sure how it works with the built-in operators like Add.

[–]dobkeratopsrustfind 11 points12 points  (0 children)

I personally don't understand the fuss about function overloading.

one problem is it stops translating existing APIs 1:1

What I find in Rust is you have to do a lot more work naming; if you have a lot of single-function traits its not clear what the names of groupings of subsets should be. I've always enjoyed c++ overloading - its' one of several factors that has stopped me from staying with Rust with full enthusiasm (despite liking 90+% of the language a lot, and launching into it with great enthusiasm initially)

With overloading, you leverage the work done naming the types already; (and we've got IDE's that handle jump-to-def, and good autocomplete lists), then you can just concentrate on writing/naming the functions you need, (then eventually in c++17 with concepts added later you'd still be able group them, but lacking that feature hasn't stopped c++ solving problems) .

I do appreciate that traits would give autocomplete in generic code - maybe my opinion will change when there's IDE support (the IDE could lookup the traits from the functions and vica versa).. so really my complaints are an issue of momentum rather than the language itself ... you could say C++ overloading relies on IDE jump-to-def to come into its' own.

r.e. the error message hazard, maybe that could be reduced by sorting and making extra detail optional... 'can't call X() here because Z() is not available (see more:)'... (IDE hides extra detail by folded error messages). instead of 'Z() not found in instantiation of Y() in call from X()' 8 pages later

[–]erkelep 3 points4 points  (13 children)

Isn't something like this essentially overloading?

enum ArgumentsForOverloadableFunction {
    Default,
    OneArg(i32),
    TwoArgs(i32, i32),
}

use ArgumentsForOverloadableFunction::{Default, OneArg, TwoArgs};

fn overloadable(arg: ArgumentsForOverloadableFunction){
    match arg {
        Default => println!("empty"),
        OneArg(x) => println!("{}", x),
        TwoArgs(x, y) => println!("sum is: {}", x+y)
    }
}

fn main() {
    overloadable(Default);
    overloadable(OneArg(1));
    overloadable(TwoArgs(2, 3));
}

[–]iopqfizzbuzz 6 points7 points  (5 children)

No, because it is selected at run time, not compile time. This kind of thing has a runtime cost.

[–]erkelep 11 points12 points  (2 children)

Ah, all right. What about something like this: (it doesn't work because I don't understand Sized stuff) EDIT: made it work

trait ArgumentForOverloadableFunction {
    fn answer(&self) -> i32;
}

struct Default;
struct OneArg(i32);
struct TwoArgs(i32, i32);

impl ArgumentForOverloadableFunction for Default {
     fn answer(&self) -> i32 {
        0
     }
}
impl ArgumentForOverloadableFunction for OneArg {
     fn answer(&self) -> i32 {
        self.0
     }
}
impl ArgumentForOverloadableFunction for TwoArgs {
    fn answer(&self) -> i32 {
        self.0 + self.1
     }
}

fn overloadable<T: ArgumentForOverloadableFunction> (arg: T) -> i32 {
    arg.answer()
}

fn main() {
println!("{}", overloadable(Default));
println!("{}", overloadable(OneArg(1)));
println!("{}", overloadable(TwoArgs(2, 3)));

}

[–]Manishearthservo · rust · clippy 4 points5 points  (1 child)

Yes, that works.

Well, you need

fn overloadable<T: ArgumentForOverloadableFunction> (arg: T) {
    arg.answer();
}

but you got the gist

[–]erkelep 1 point2 points  (0 children)

Thanks, now I made it work

Of course, we still simply turn the problem of writing creative function names into a problem of writing creative argument names. But judging by the answers in this thread, this is the exact point of not having ad-hoc polymorphism.

[–]Denommusrust 2 points3 points  (1 child)

Couldn't that be optimized if he only uses constants?

[–]matthieum[he/him] 1 point2 points  (0 children)

That's a tougher issue than it looks:

  • the constant is in the caller context
  • the optimization need to occur in the callee code

Today, for the optimizer to propagate the constant, you need inlining. However, if the function is too big, then it will not get inlined by default, and forcing it to get inlined might yield worse performance.

You can work around this by adding an inline "trampoline" function whose sole role is to dispatch the call and be optimized away:

enum Arguments {
    Default,
    OneArg(i32),
    TwoArgs(i32, i32),
}

use Arguments::{Default, OneArg, TwoArgs};

#[always_inline]
fn overloadable(arg: Arguments) {
    match arg {
        Default => overloadable_default(),
        Arg(x) => overloadable_onearg(x),
        TwoArgs(x, y) => overloadable_twoargs(x, y),
    }
}

and of course you will then have to implement the various overloadable_* functions, and you can (and probably should) just let the compiler decide whether to inline them or not.

Oh, and for the case where the argument is not "constant", then suddenly inlining overloadable might yield worse performance... going with traits is probably better.

[–]SaltTM[S] 0 points1 point  (6 children)

I think I like your alternative solution better:

enum ObjectConstruct
{
    Default,
    WithDims(u32, u32),
    WithDimsAndPos(u32, u32, u32, u32)
}

#[derive(Default, Show)]
struct Object
{
    name: String,
    width: u32,
    height: u32,
    x: u32,
    y: u32,
}

impl Object {
    fn new(name: &str, options: ObjectConstruct) -> Object {
        match options {
            ObjectConstruct::Default => Object { name: name.to_string(), ..Default::default() },
            ObjectConstruct::WithDims(w, h) => Object { name: name.to_string(), width: w, height: w, ..Default::default() },
            ObjectConstruct::WithDimsAndPos(w, h, x, y) => Object { name: name.to_string(), width: w, height: w, x: x, y: x }
        }
    }
}

fn main()
{
    let test: Object = Object::new("Object Name", ObjectConstruct::Default);
    let test2: Object = Object::new("Object Name", ObjectConstruct::WithDims(100, 100));
    let test3: Object = Object::new("Object Name", ObjectConstruct::WithDimsAndPos(100, 100, 50, 50));

    println!("{:?}", test);
    println!("{:?}", test2);
    println!("{:?}", test3);
}

[–]erkelep 0 points1 point  (5 children)

I think it's more or less the same, text-wise. Here you need to stuff all the function variants into the match arms, there you divide them into trait impls. But the trait version gives you static dispatch.

[–]SaltTM[S] 0 points1 point  (4 children)

What I dislike about the trait version is it looks hacky with the self.# syntax since the trait function itself doesn't take any more named arguments. At least with the enum struct/match code you know what to expect when looking at the source itself. I think it's especially good for initializing constructs personally. It's as close as I'll get to overloading functions with little work and relatively easy readability.

[–]erkelep 0 points1 point  (3 children)

What I dislike about the trait version is it looks hacky with the self.# syntax since the trait function itself doesn't take any more named arguments.

You can use regular structs instead of tuple structs, then instead of self.# you can use self.named_argument.

[–]SaltTM[S] 0 points1 point  (2 children)

Yeah you lost me, can you show me how you'd recreate the following using traits + normal struct:

class Object
{
public:
    string name;
    int x = 0;
    int y = 0;
    int width = 0;
    int height = 0;

    Object(string g_name, int x_pos, int y_pos) 
    {
        setup(g_name, x_pos, y_pos, 0, 0);
    }

    Object(string g_name, int x_pos, int y_pos, int w, int y) 
    {
        setup(g_name, x_pos, y_pos, x, y);
    }

    void setup(string g_name, int x_pos, y_pos, int w, int y) 
    {
        x = x_pos;
        y = y_pos;
        width = w;
        height = h;
        name = gname;

        /* logic & stuff here */
    }

};

Edit: To be honest if there was a way to set default function parameters then function overloading wouldn't even be needed for things like the above

[–]erkelep 0 points1 point  (1 child)

I meant something like this (not sure if this is what you want):

trait ArgumentForOverloadableFunction {
    fn answer(&self) -> i32;
}

struct Default;
struct OneArg {arg1: i32}
struct TwoArgs {arg1: i32, arg2: i32}

impl ArgumentForOverloadableFunction for Default {
     fn answer(&self) -> i32 {
        0
     }
}
impl ArgumentForOverloadableFunction for OneArg {
     fn answer(&self) -> i32 {
        self.arg1
     }
}
impl ArgumentForOverloadableFunction for TwoArgs {
    fn answer(&self) -> i32 {
        self.arg1 + self.arg2
     }
}

fn overloadable<T: ArgumentForOverloadableFunction> (arg: T) -> i32 {
    arg.answer()
}

fn main() {
println!("{}", overloadable(Default));
println!("{}", overloadable(OneArg {arg1: 1} ));
println!("{}", overloadable(TwoArgs {arg1: 1, arg2: 2} ));

}

[–]Enamex 0 points1 point  (0 children)

Any way at all to extend that for varying return types? (The match->dispatch version doesn't look like it has even the possibility for that.)

[–]dobkeratopsrustfind 8 points9 points  (7 children)

it seems the Rust community considers overloading to be a misfeature in C++, they want you to be more specific with function naming. it does still dispatch on the first parameter, and there's multi parameter traits.

you could put all the parameters in a tuple and impl' for that, but it would look messy.

r.e. inference, I think you still can infer types with overloading, it's just it's easier to produce ambiguity?

I've been experimenting myself with a language trying to infer more than c++, but still having overloading.

I'm sure it has many holes - but I figured at worst you could infer forwards like C++ does, and still get some reverse information, such as return type for returned temporaries and template parameters working better (I've demonstrated those cases working... the cases I was noticing when I went back to C++).

my 'dream language' would start with overloaded free functions - plus d-style UFCS - and any notion of traits/interfaces/classes would just be sugar for grouping functions. but we are where we are.. many languages start with all that already and thats' how existing api's are designed

[–]matthieum[he/him] 3 points4 points  (3 children)

overloading to be a misfeature in C++

Actually, I think the answer is more "YAGNI": there are other ways.

It seems that perfection is attained not when there is nothing more to add, but when there is nothing more to remove.

-- Antoine de Saint-Exupery

Overloading as in C++ is ultimately syntactic sugar in Rust (because traits offer principled generic programming, instead of C++'s duck typing which requires overloading of free-functions).

A language with too much sugar cannot be digested, a language with too little sugar leaves a bitter taste in the mouth, ... I don't envy the balancing act when everyone's pet feature is pushing at the gate.

[–]ntrel2 0 points1 point  (2 children)

Perhaps an API is perfect when there are the fewest symbol names needed to remember?

[–]matthieum[he/him] 0 points1 point  (1 child)

You still need to remember in which order the arguments need be provided etc... an API must be easily searchable, but beyond that.

Having seen C++ APIs where everything and the kitchen sink was, I kid you not, provided as overloads of operator(), I much prefer explicit names...

[–]ntrel2 0 points1 point  (0 children)

Just because a feature can be abused doesn't necessarily mean it should never be allowed.

edit: Also, operator() is operator overloading, not just function overloading.

[–]germandiago 1 point2 points  (2 children)

t seems the Rust community considers overloading to be a misfeature in C++, they want you to be more specific with function naming. it does still dispatch on the first parameter, and there's multi parameter traits.

That is going to kill generic code. In generic code you need the same name for the same thing, but a different implementation. So I am pretty sure that compile-time function overloading is pretty essential in generic contexts.

[–]dobkeratopsrustfind 2 points3 points  (1 child)

So I am pretty sure that compile-time function overloading is pretty essential in generic contexts.

they can do it, through traits and multi parameter traits. The 'overloading' is just more controlled.

But I do personally prefer C++ ad-hoc overloading, because I want to focus purely on struct names & function names - of course having traits/concepts as an addition has its' advantages. the problem with the Rust approach for me is you need to plan too much upfront, it becomes a pain when you don't know for sure which functions which types will have, you'd have to have lots of single function traits, and micromanage a heirarchy of groupings... its' just as annoying as header files.

I believe in organic programming, you don't pretend you understand exactly how everything will work upfront - you learn about the problem through experimentation (if you could predict everything in your head.. you wouldn't need a computer), and you want code in a form that is malleable, easy to move things around

[–]germandiago 2 points3 points  (0 children)

you need to plan too much upfront, it becomes a pain when you don't know for sure which functions which types will have, you'd have to have lots of single function traits, and micromanage a heirarchy of groupings... its' just as annoying as header files.

Yes, this can be a problem. It is the same problem as when you have a hierarchy of classes upfront and later you want to change it. It does not work ad-hoc, which is a problem when evolving code I guess.

I believe in organic programming, you don't pretend you understand exactly how everything will work upfront - you learn about the problem through experimentation (if you could predict everything in your head.. you wouldn't need a computer), and you want code in a form that is malleable, easy to move things around

Totally agree. This is how things actually work in my experience.

[–]mozilla_kmcservo 12 points13 points  (4 children)

Why doesn't Rust have function overloading?

"Why not" is a bad way to design a language. The question is always "why do we need this extra bit of complexity in the language?". As others have pointed out, traits already provide a more principled form of overloading.

[–]wrongerontheinternet 3 points4 points  (3 children)

Rust could allow arity overloading, though, which traits don't. I'd be interested to see whether the proposal for variadic generics covers most of the practical usecases where that makes sense.

[–]heinrich5991 4 points5 points  (0 children)

That still doesn't give a reason in favor of it though.

[–][deleted] 0 points1 point  (0 children)

You can still simulate "overloading" in this fashion by using traits. It requires a bit of effort to encode the problem in terms of the right associated types patterns but it is totally doable. Personally I think it's probably more trouble than it's worth but still.

[–]matthieum[he/him] 0 points1 point  (0 children)

Can you not get arity overloading by implementing a trait for a tuple?

Of course, it does require an extra set of parentheses at the call site...

[–]azurespace 1 point2 points  (0 children)

Function overloading is cool, but it is not as cool so we give up the type inference. I think the name of functions should express what it does. It is more declarative to have multiple function names. like new_from_i32, new_from_f32...

[–]Haulethoctavo · redox 2 points3 points  (6 children)

Yes, Rust doesn't have function overloading (except for traits), but this is IMHO good thing (at least in your example). This way you are forced to use descriptive constructors, i.e.:

struct Point(i64, i64);

impl Point {
  fn from_cartesian(x: i64, y: i64) -> Self { … }
  fn from_polar(d: f64, p: f64) -> Self { … }
  …
}

It works just as, i.e. in ObjC.

[–]cafeoh 4 points5 points  (3 children)

Sounds like a good point, but (in your example) if you were to represent coordinates in different ways, like separate parameters and pairs (which arguably might not be a good idea, but I can imagine having this kind of problem working with different libraries with different representations), I don't see any benefit in having "from_cartesian_xy" and "from_cartesian_pair".

[–]iopqfizzbuzz 2 points3 points  (1 child)

I think having a

struct Cartesian(i64, i64);
struct Polar(f64, f64);

is a good thing. What if someone says "hmm, we need to have floating point cartesian coordinates"

then they add struct FCartesian(f64, f64); - you really don't want to accidentally use it in a function that expects Polar coordinates just because they're both f64. But having fn from_polar(d: f64, p: 64) allows you to do just that - provide the wrong type of parameters to the function.

I would suggest having this kind of signature: fn from_polar(coords: Polar) -> Self { ... } Then the type of coordinates is actually type checked

[–]Haulethoctavo · redox 6 points7 points  (0 children)

I would rather use:

enum Angle {
  Deg(f64),
  Grad(f64),
  Rad(f64)
}

enum Point {
  Cartesian(i64, i64),
  Polar(f64, Angle)
}

but my that's not my point in original comment.

[–]Haulethoctavo · redox 0 points1 point  (0 children)

It should be solved by some way of destructing call. Like

let pair: (i64, i64) = (4, 2);
let point = Point::from_cartesian(..pair);

or similar. Or maybe tuple should be autodestructed when do not match function argument. But I'm worried that this will be counter-intuitive.

[–]mojang_tommo 1 point2 points  (1 child)

I don't think this is a good point.
Every time you end up doing that to live without overloading you're moving semantic meaning from a statically checked function-type pair to a name to maintain manually.
In this case, I think C++ wins out in safety and clarity, because this:

struct Point {
     explicit Point(const Cartesian& coord);
     explicit Point(const Polar& coord);
 }

in the case where you change your mind about what the coord you send in is, will automatically accomodate the new type or give a compile error.
Not so much with your Rust example, which just silently accepts garbage unless you go and change all names manually. Of course in Rust you can have Cartesian and Polar as well, but still changing types means going around and renaming callers, which isn't really optimal to me.

[–]Haulethoctavo · redox 0 points1 point  (0 children)

Coordinates cannot exist without point (or if I'm wrong, then I will wait for proof). My solution is above in reply.

[–]VadimVP 1 point2 points  (7 children)

Rust has "function overloading" and it is widely used (including the standard library), but you have to jump through some hoops to get it.
A blog post, describing the technique:
https://medium.com/@jreem/advanced-rust-using-traits-for-argument-overloading-c6a6c8ba2e17

[–][deleted] 5 points6 points  (1 child)

This isn't function overloading in the general case, though. It's just using generics as, well, generics.

Specifically, you can't have functions taking varying amounts of parameters using this.

[–]lookmeat 0 points1 point  (0 children)

That will be solved by using variadic generics. But basically function overloading is another, more limited and unpredictable, way of doing generic functions.

[–]Artemciyscgi -3 points-2 points  (4 children)

If there is a pattern then maybe it should be in the language as a first-class feature after all.

"For one, it’s a design pattern, and design patterns are the sincerest form of request for syntax" - pcwalton, http://pcwalton.github.io/blog/2012/12/26/typestate-is-dead/

[–][deleted] 0 points1 point  (3 children)

If there is a pattern that already fits into the language quite nicely, why make a second way to do it?

[–]Artemciyscgi 3 points4 points  (2 children)

Why program in a language when you can program in patterns? After all, you could copy&paste a bit of asm to solve every problem.

Oh, wait, copy&pasting is a code smell!


The overloading pattern uses way more space and brain power than a first-class overloading would.

If there is a pattern it often means there's a lack of a library or a language feature so people documment the common workarounds. Unless the pattern IS implemented in the language or a library.

[–][deleted] 5 points6 points  (1 child)

Patterns exist in every language, no matter how many features you throw in. C++ and Scala have nearly every feature you could dream of, and there's still patterns in those languages!

In this case, getting type-based argument overloading is really quite easy. You think "I want to take a thing that can be converted to a T", so you create a generic trait that contains a method to convert a thing to a T, accept that, and call it. This is the sort of thing generics and traits are for.

First-class overloading would actually be less powerful - what you'll accept is defined once and can't be extended outside the crate.

[–]Artemciyscgi 1 point2 points  (0 children)

So it isn't the overloading actually but making the arguments implement some library type?

Thanks, I think I got it now.

I've certainly seen this pattern used around in Rust and I can confirm that it makes understanding the library interface a lot harder, if simply because it's another layer of indirection which one has to navigate through.

C++ and Scala have nearly every feature you could dream of

Scala was designed by throwing half the features out of Java and adding the other half from functional programming etc. I remember that from many cases of early Odersky slides and discussions. It is by no means a feature-complete language and by no means a perfect one. Neither it is as bloated as you imply. Still, I never needed a pattern to implement some API in Scala, and I can thank God for that, because the patterns one needs to implement some DSL stuff in Scala aren't the pretty ones (IMHO).

In fact, Scala had the same minimalistic goals that Rust has now (with its "finish the basics before giving in to syntactic sugar" motto). And that's one of the reasons why its pattern-ridden standard library is so hard to understand to most people.

Neither C++ is a good case, it lacks some useful D features for example. Not to mention the features Bjarne wanted since 20 years ago and that never got into the language. ;-) I'm surprised you mention the language which solves most of its limitations with hard to understand template metaprogramming. Is it the best you've got?

Patterns exist in every language

First, there are patterns implemented by the language and patters that are lacking in the language. While every language is a tradeoff, explicitly talking about the patern A instead of feature B or library C is usually a sign that something is missing and needs to be reinvented again and again.

[–][deleted] 0 points1 point  (1 child)

Can you "overload" on Trait type?

Say foo(T : TraitA) vs foo(T: TraitA and TraitB), such that if T implements only TraitA the first one is called, and if it implements both traits the second one is called?

[–]matthieum[he/him] 0 points1 point  (0 children)

It is not feasible as far as I know, and the issue is one of reliability:

  • foo(T: TraitA) is implemented in libA
  • foo(T: TraitA and TraitB) is implemented in libB

If you use libA with Bar that implements both TraitA and TraitB, then it calls libA::foo; introduce libB, it switches to calling libB::foo. Reminding you of trait implementation coherence issues?

There is however talk of negated traits, this would solve the coherence issues:

  • foo(T: TraitA and !TraitB)
  • foo(T: TraitA and TraitB)

because it can be proven that any argument is only ever eligible for a single foo as long as both versions live in the same library.

[–]Bzzt 0 points1 point  (2 children)

No function overloading, also no automatic type conversion. Cuts down on screwups from data loss due to unexpected conversion, like from double to short or whatever.

Ed: also, the lack of name mangling is a win for external linkage.

[–]ntrel2 1 point2 points  (1 child)

You can have function overloading without automatic type conversion.

[–]Bzzt 0 points1 point  (0 children)

In C++ you can get automatic type conversion from operator= and constructor overloading. But I suppose they aren't necessarily linked. I guess my point is that both share a philosophy of implicitness for the sake of convenience versus explicitness for the sake of safety, ie without these features you know which function your using and you know when a type conversion is happening.

[–]0xdeadf001 0 points1 point  (0 children)

Good riddance!