RFC: Finalize syntax and parameter scoping for impl Trait, while expanding it to arguments by aturon · Pull Request #1951 · rust-lang/rfcs (original) (raw)

The RFC is already merged, but is there any chance we could rethink the decision to introduce impl Trait in argument position?

I'd like to respond to some of the arguments provided in the RFC to explain why I dislike the idea of having impl Trait in argument position:

Argument from learnability

Now, consider a new Rust programmer, who has learned about generics:

fn take_iter<T: Iterator>(t: T)

What happens when they want to return an unstated iterator instead? It's pretty natural to reach for:

fn give_iter<T: Iterator>() -> T

I'm not sure about this. I actually don't see a reason why somebody would do that, because you don't want it to work with every type implementing Iterator. Also, @steveklabnik explains this very clear in the book:

Sometimes, when writing a function or data type, we may want it to work for multiple types of arguments. In Rust, we can do this with generics.

from TRPL, chapter about generics

So it's also clearly about working with "multiple types". Using impl Trait as return type, however, is clearly a way of letting the compiler infer the actual type, but providing certain guarantees which traits it implements. If I were to see impl Trait in argument position without knowing about these plans, I'd actually think it's some clever type inference by the compiler, not generic over types implementing that trait.

Now, let's assume it actually would make it a bit easier for a beginner.

  1. While it's very important to be easy to learn, I'd say that impl Trait in argument position is more of a response to a beginners misunderstanding than a good feature in the language. I don't think avoiding one mistake of a beginner is a reason to have a "half-broken" language feature (please excuse the description as "half-broken", I don't know how to describe it more appropriately).
  2. It essentially allows the beginner to proceed with a wrong mental model (and that their code works approves this model).

Argument from ergonomics

This one is basically about

fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Option

being more ergonomic than

fn map(self, f: impl FnOnce(T) -> U) -> Option

First of all, I feel this is a rather weak argument. While I agree that you get the meaning of f in the latter one a tad faster, I don't agree that it's worth having this feature.

Another point I'd like to raise here is that there is no comparison to using a where-clause:

fn map<U, F>(self, f: F) -> Option where F: FnOnce(T) -> U

fn map(self, f: impl FnOnce(T) -> U) -> Option

It might be highly subjective, but I find the one with where actually the most easy one to read. I'd like to hear other opinions on this, probably somebody else doesn't feel this way.

Argument from familiarity

In Java, non-static methods aren't parametric; generics are used at the type level, and you just use interfaces at the method level.

I don't agree here. Consider the following:

trait Foo {}

fn foo(v: Vec)

vs

interface Foo {}

void foo(ArrayList list)

While the Rust example expects a Vec containing elements of some type implementing Foo (so all elements being of the same type), the Java example accepts a heterogeneous collection.

I don't know how to reply to the other part of the argument, because I'm not sure how it is related to impl Trait.

Stylistic overhead

As mentioned in the RFC, there was a point raised by @nrc about it being stylistic overhead / one more syntax to learn.

Now, this is a rather weak argument on my side, but

  1. it's true that it's one more syntax, although the learning effort is very small. I agree with your argument that it's not a new concept.
  2. [..] since expanding out an argument list into multiple lines tends to be preferable to expanding out a where clause to multiple lines [..]

I personally prefer the where syntax here again (it seems the RFC doesn't use the rustfmt formatting here), but again, this is just a matter of taste, so I wouldn't see any of these arguments valid to make the decision.


In the end, I'd like to rephrase your words that "it's not a new concept" but rather a new syntax for an existing concept. So it essentially boils down to the question whether or not the syntax is better and whether or not it's good to have 3 alternative syntaxes.

For me, that's a clear no, given that I find it more confusing from the perspectives I explained above, namely

@aturon Please don't take this personal, you're doing great work! I just wanted to give my opinion here, as a Rust library developer. Also note that I don't know very much about type theory, so I could be missing substantial facts which invalidate some of my arguments; if that's the case, please tell me and I'll remove them.

Thanks for reading!