If you're not interested in reading about the prior work done on
impl Trait, feel free to skip ahead to the fourth and final post.
The concepts behind
impl Trait have been around for a surprisingly long time. I've done my best to collect relevant RFCs, successful and failed, review them and compare them to my proposal. There are five RFCs that are directly relevant to
impl Trait and type abstraction. Let's go in chronological order.
In many ways, this RFC has the same goals as my recommendation. Specifically, it addresses the following by name:
It even proposed allowing
impl Trait in any type position, which
as impl Trait also aims to allow. Thare are places in the thread where
as impl Trait is even used, albeit in the context of a value. So why was the more conservative version accepted instead of the original?
The original proposal didn't have a concrete use-case for
impl Trait outside of function signatures. With a more current need for TAIT, I would say that we now have a motivating use case for it.
The original proposal suggested syntax like
collect_to_set::<T, I>::impl to refer to the
impl Trait return type of the function
collect_to_set. This raised questions about how to refer to nested return types.
as impl Trait does not have this problem, since the correct approach in this case would be to separate the return type into a type alias. It is good to question how named unnameable types interact with distant type aliases though and whether this would be an issue. Nested
as impl Traits can be approached in the same way.
There was a lot of concern that it would be easy to confuse
impl Trait (a statically-dispatched abstract type) with
Trait. At the time,
Trait was the syntax for trait objects. It has since been replaced with
dyn Trait and deprecated, so the risk of confusion is lower now than ever. Additionally, I think that
Type as impl Trait conveys a much larger difference from
dyn Trait than
impl Trait does.
impl Traitis not powerful enough
impl Trait was proposed as being a very general-purpose equivalent of existential types, which
as impl Trait does not. There was some reluctance to having two ways to express the same concept, which isn't as much of a problem with
as impl Trait.
@pnkfelix actually calls this out explicitly in a comment, how
impl Trait has two different uses depending on whether it's used in argument position or return position. There's also discussion about how covariant and contravariant appearances of
impl Trait have different desugarings, and even a suggestion that
some Trait be introduced to capture that notion. This parallels a lot of what was discussed in the first post.
This was a controversial aspect at the time, but it's now well-known that abstractions leak auto traits. This hasn't changed, and the previous discussion is extensive and not worth rehashing.
At the end of it all, @aturon closed the RFC in favor of working towards some alternatives with other interested members.
This first RFC does somewhat of a whirlwind tour of the problems that
impl Trait attempts to solve. Existential types made an appearance, conditional trait bounds showed up, and there was speculative syntax galore. It properly sets the stage for:
This RFC is essentially just a limited version of the original proposal. It only allows
impl Trait in argument and return position on free and inherent functions. Unfortunately, there's not much to comment on here since it's a subset of the previous RFC. Some notes:
There are a few places here where the terminology around these types are questioned. I prefer "abstract" to draw a clearer separation between abstracted types and unnameable types, both of which could be confused as "anonymous" by non-experts (sorry @eddyb!).
@eddyb suggests naming the return types of functions, which is very similar to my suggestion for named unnameable types. Even down to the use of the
type keyword, which was fun to see. Naming the return types of functions alone would make it possible to do everything that type alias
as impl Trait can do, but using it in conjunction with type aliases would give us incredibly comfy ergonomics.
The downsides of
impl Trait are known, and a few people pointed out that encouraging the use of
impl Trait in more places could lead to undesirable situations. We discussed this problem when we talked about whether we want our trait implementations to leak, and I think that
as impl Trait alleviates many of these concerns by preserving the concrete type information.
There seem to be some strong opinions about which syntax would be better.
impl Trait won evidently.
A really poignant critique of the RFC was that the original failed because many people wanted different futures for it. This RFC was effectively just the lowest common denominator, but doesn't resolve the question of which future
impl Trait should have. This is exactly the same problem that I aim to address with
as impl Trait.
@glaebhoerl suggests using
for blocks to desugar return-position
impl Trait for generic free functions. It's a cool idea, but doesn't get much further exploration as the RFC period was winding down.
This RFC, in essence, took on all the bikeshedding that wasn't ultimately essential to the first RFC. It does manage to introduce
impl Trait in argument position, resolve bikeshedding around syntax, and come to a conclusion about type and lifetime parameters interacting with
There's honestly not too much to discuss here that hasn't already been discussed. Most of this RFC is consensus building and cornering
impl Trait to prevent it from getting out of hand. This is a valuable RFC to read if you want to understand the specific semantics of
impl Traitexistential types
We've finally caught up and are moving on to the more experimental RFCs. This RFC aims to introduce existential types and expand
impl Trait to more places. I have some real critiques of this RFC, so let's get into them.
as impl Traitin spirit
One of the first sections of this RFC suggests this syntax for abstracted types:
let displayable: impl Display = "Hello, world!";
This doesn't include the concrete underlying type, and so I think this would be much clearer as:
let displayable: &'static str as impl Display = "Hello, world!"; // or, with variable type inference let displayable: _ as impl Display = "Hello, world!";
This addresses the same problems, but with an arguably clearer syntax:
// Concrete const DISPLAYABLE: &'static str = "Hello, world!"; // Abstract const DISPLAYABLE: &'static str as impl Display = "Hello, world!";
And can handle unnameable types with local inference:
const MY_CLOSURE: _ as impl Fn(i32) -> i32 = |x| x + 1;
This section in particular muddies the water with
impl Trait. The introduction of existential types is intended to provide the same type-abstracting functionality as
impl Trait, but with the added benefit of being usable in more positions. There is a particular focus on type aliases and associated types, where the concrete underlying type is inferred based on its use.
I believe that inferring this concrete type is a mistake, and leads to the same inference issues discussed in the last post. Additionally, there is a large focus on not naming the concrete type to prevent trait impls from leaking. However I think this confuses the abstraction of a type with the naming of it. It's clearer to name a type and explicitly abstract it.
Fundamentally, I believe that existential types are a less clear formulation of
as impl Trait. That's a very subjective opinion.
Finally, this RFC is nice and light. It simply builds on the existential types RFC by explicitly setting the syntax to use
impl Trait. There is a lot of language lawyering and nailing down the very specific semantics, and it does a good job of explaining why additional syntax hinders learnability and results in confusion. We've already discussed the pros and cons of this RFC extensively, and I think we can do better.
impl Trait has taken a long journey to reach where it is now. Along the way there has been great ambition, as well as great confusion. I understand that there are very strong convictions on both sides, and I hope that we can use this as an opportunity to finally resolve them.
In my final post, I hope to bring the past three posts together into a coherent framework and provide a final recommendation on what should be done with
impl Trait. If you've made it this far, I appreciate it a lot and hope you can hang on for just a little while longer.