impl Trait
in argument position is functionally identical to a generic parameter. It used to be that you couldn't turbofish a function at all that used APIT, but now you can for the explicit generic parameters without specifying the implied generic parameter from the impl Trait
.
So I much prefer using impl Trait
for types that I usually can't turbofish anyway, since it makes things cleaner.
Take this function, for example:
fn foo<T, F: Fn() -> T>(f: F) { … }
If I want to annotate the T
explicitly, I have to write something like this:
foo::<u64, _>(|| 3);
But if I choose instead to define the function using this:
fn bar<T>(f: impl Fn() -> T) { … }
Then I don't have to (and can't) put anything for the type of the closure, and can just write
bar::<u64>(|| 3);
Which is way nicer, since I can never actually write out the type of a function anyway. (I could pass along a generic, but I almost never need to do that because I have an instance I can just pass that makes it infer it correctly anyway.)
And just in general, it's pretty common with closures and iterators that you don't really care what exact type you have, and you don't need to mention it again later, in which case mixing explicit generics and argument-position impl trait can make it nicer to read the signatures.
For example, I find that a definition like this reads really well:
fn merge<T: Ord>(left: impl Iterator<Item = T>, right: impl Iterator<Item = T>) -> impl Iterator<Item = T>;`
I think of that as "being generic over T, but taking two iterators", even though it's generic over three different things in the implementation details.
There are other ways that could be written, like
fn merge<L: Iterator, R: Iterator<Item = L::Item>>(left: L, right: R) -> impl Iterator<Item = L::Item>
But that just reads so much worse to me.