Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stabilize generic associated types #96709

Merged
merged 1 commit into from Sep 13, 2022
Merged

Conversation

jackh726
Copy link
Member

@jackh726 jackh726 commented May 4, 2022

Closes #44265

r? @nikomatsakis

⚡ Status of the discussion ⚡

Stabilization proposal

This PR proposes the stabilization of #![feature(generic_associated_types)]. While there a number of future additions to be made and bugs to be fixed (both discussed below), properly doing these will require significant language design and will ultimately likely be backwards-compatible. Given the overwhelming desire to have some form of generic associated types (GATs) available on stable and the stability of the "simple" uses, stabilizing the current subset of GAT features is almost certainly the correct next step.

Tracking issue: #44265
Initiative: https://rust-lang.github.io/generic-associated-types-initiative/
RFC: https://github.com/rust-lang/rfcs/blob/master/text/1598-generic_associated_types.md
Version: 1.65 (2022-08-22 => beta, 2022-11-03 => stable).

Motivation

There are a myriad of potential use cases for GATs. Stabilization unblocks probable future language features (e.g. async functions in traits), potential future standard library features (e.g. a LendingIterator or some form of Iterator with a lifetime generic), and a plethora of user use cases (some of which can be seen just by scrolling through the tracking issue and looking at all the issues linking to it).

There are a myriad of potential use cases for GATs. First, there are many users that have chosen to not use GATs primarily because they are not stable (some of which can be seen just by scrolling through the tracking issue and looking at all the issues linking to it). Second, while language feature desugaring isn't blocked on stabilization, it gives more confidence on using the feature. Likewise, library features like LendingIterator are not necessarily blocked on stabilization to be implemented unstably; however few, if any, public-facing APIs actually use unstable features.

This feature has a long history of design, discussion, and developement - the RFC was first introduced roughly 6 years ago. While there are still a number of features left to implement and bugs left to fix, it's clear that it's unlikely those will have backwards-incompatibility concerns. Additionally, the bugs that do exist do not strongly impede the most-common use cases.

What is stabilized

The primary language feature stabilized here is the ability to have generics on associated types, as so. Additionally, where clauses on associated types will now be accepted, regardless if the associated type is generic or not.

trait ATraitWithGATs {
    type Assoc<'a, T> where T: 'a;
}

trait ATraitWithoutGATs<'a, T> {
    type Assoc where T: 'a;
}

When adding an impl for a trait with generic associated types, the generics for the associated type are copied as well. Note that where clauses are allowed both after the specified type and before the equals sign; however, the latter is a warn-by-default deprecation.

struct X;
struct Y;

impl ATraitWithGATs for X {
    type Assoc<'a, T> = &'a T
      where T: 'a;
}
impl ATraitWithGATs for Y {
    type Assoc<'a, T>
      where T: 'a
    = &'a T;
}

To use a GAT in a function, generics are specified on the associated type, as if it was a struct or enum. GATs can also be specified in trait bounds:

fn accepts_gat<'a, T>(t: &'a T) -> T::Assoc<'a, T>
  where for<'x> T: ATraitWithGATs<Assoc<'a, T> = &'a T> {
    ...
}

GATs can also appear in trait methods. However, depending on how they are used, they may confer where clauses on the associated type definition. More information can be found here. Briefly, where clauses are required when those bounds can be proven in the methods that construct the GAT or other associated types that use the GAT in the trait. This allows impls to have maximum flexibility in the types defined for the associated type.

To take a relatively simple example:

trait Iterable {
    type Item<'a>;
    type Iterator<'a>: Iterator<Item = Self::Item<'a>>;

    fn iter<'x>(&'x self) -> Self::Iterator<'x>;
    //^ We know that `Self: 'a` for `Iterator<'a>`, so we require that bound on `Iterator`
    //  `Iterator` uses `Self::Item`, so we also require a `Self: 'a` on `Item` too
}

A couple well-explained examples are available in a previous blog post.

What isn't stabilized/implemented

Universal type/const quantification

Currently, you can write a bound like X: for<'a> Trait<Assoc<'a> = &'a ()>. However, you cannot currently write for<T> X: Trait<Assoc<T> = T> or for<const N> X: Trait<Assoc<N> = [usize; N]>.

Here is an example where this is needed:

trait Foo {}

trait Trait {
    type Assoc<F: Foo>;
}

trait Trait2: Sized {
    fn foo<F: Foo, T: Trait<Assoc<F> = F>>(_t: T);
}

In the above example, the caller must specify F, which is likely not what is desired.

Object-safe GATs

Unlike non-generic associated types, traits with GATs are not currently object-safe. In other words the following are not allowed:

trait Trait {
    type Assoc<'a>;
}

fn foo(t: &dyn for<'a> Trait<Assoc<'a> = &'a ()>) {}
         //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not allowed

let ty: Box<dyn for<'a> Trait<Assoc<'a> = &'a ()>>;
          //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not allowed

Higher-kinded types

You cannot write currently (and there are no current plans to implement this):

struct Struct<'a> {}

fn foo(s: for<'a> Struct<'a>) {}

Tests

There are many tests covering GATs that can be found in src/test/ui/generic-associated-types. Here, I'll list (in alphanumeric order) tests highlight some important behavior or contain important patterns.

  • ./parse/*: Parsing of GATs in traits and impls, and the trait path with GATs
  • ./collections-project-default.rs: Interaction with associated type defaults
  • ./collections.rs: The Collection pattern
  • ./const-generics-gat-in-trait-return-type-*.rs: Const parameters
  • ./constraint-assoc-type-suggestion.rs: Emit correct syntax in suggestion
  • ./cross-crate-bounds.rs: Ensure we handles bounds across crates the same
  • ./elided-in-expr-position.rs: Disallow lifetime elision in return position
  • ./gat-in-trait-path-undeclared-lifetime.rs: Ensure we error on undeclared lifetime in trait path
  • ./gat-in-trait-path.rs: Base trait path case
  • ./gat-trait-path-generic-type-arg.rs: Don't allow shadowing of parameters
  • ./gat-trait-path-parenthesised-args.rs: Don't allow paranthesized args in trait path
  • ./generic-associated-types-where.rs: Ensure that we require where clauses from trait to be met on impl
  • ./impl_bounds.rs: Check that the bounds on GATs in an impl are checked
  • ./issue-76826.rs: Windows pattern
  • ./issue-78113-lifetime-mismatch-dyn-trait-box.rs: Implicit 'static diagnostics
  • ./issue-84931.rs: Ensure that we have a where clause on GAT to ensure trait parameter lives long enough
  • ./issue-87258_a.rs: Unconstrained opaque type with TAITs
  • ./issue-87429-2.rs: Ensure we can use bound vars in the bounds
  • ./issue-87429-associated-type-default.rs: Ensure bounds hold with associated type defaults, for both trait and impl
  • ./issue-87429-specialization.rs: Check that bounds hold under specialization
  • ./issue-88595.rs: Under the outlives lint, we require a bound for both trait and GAT lifetime when trait lifetime is used in function
  • ./issue-90014.rs: Lifetime bounds are checked with TAITs
  • ./issue-91139.rs: Under migrate mode, but not NLL, we don't capture implied bounds from HRTB lifetimes used in a function and GATs
  • ./issue-91762.rs: We used to too eagerly pick param env candidates when normalizing with GATs. We now require explicit parameters specified.
  • ./issue-95305.rs: Disallow lifetime elision in trait paths
  • ./iterable.rs: Iterable pattern
  • ./method-unsatified-assoc-type-predicate.rs: Print predicates with GATs correctly in method resolve error
  • ./missing_lifetime_const.rs: Ensure we must specify lifetime args (not elidable)
  • ./missing-where-clause-on-trait.rs: Ensure we don't allow stricter bounds on impl than trait
  • ./parameter_number_and_kind_impl.rs: Ensure paramters on GAT in impl match GAT in trait
  • ./pointer_family.rs: PointerFamily pattern
  • ./projection-bound-cycle.rs: Don't allow invalid cycles to prove bounds
  • ./self-outlives-lint.rs: Ensures that an e.g. Self: 'a is written on the traits GAT if that bound can be implied from the GAT usage in the trait
  • ./shadowing.rs: Don't allow lifetime shadowing in params
  • ./streaming_iterator.rs: StreamingIterator(LendingIterator) pattern
  • ./trait-objects.rs: Disallow trait objects for traits with GATs
  • ./variance_constraints.rs: Require that GAT substs be invariant

Remaining bugs and open issues

A full list of remaining open issues can be found at: F-generic_associated_types `#![feature(generic_associated_types)]` a.k.a. GATs

There are some known-bug tests in-tree at src/test/ui/generic-associated-types/bugs.

Here I'll categorize most of those that GAT bugs (or involve a pattern found more with GATs), but not those that include GATs but not a GAT issue in and of itself. (I also won't include issues directly for things listed elsewhere here.)

Using the concrete type of a GAT instead of the projection type can give errors, since lifetimes are chosen to be early-bound vs late-bound.

In certain cases, we can run into cycle or overflow errors. This is more generally a problem with associated types.

Bounds on an associatd type need to be proven by an impl, but where clauses need to be proven by the usage. This can lead to confusion when users write one when they mean the other.

We sometimes can't normalize closure signatures fully. Really an asociated types issue, but might happen a bit more frequently with GATs, since more obvious place for HRTB lifetimes.

When calling a function, we assign types to parameters "too late", after we already try (and fail) to normalize projections. Another associated types issue that might pop up more with GATs.

We don't fully have implied bounds for lifetimes appearing in GAT trait paths, which can lead to unconstrained type errors.

Suggestion for adding lifetime bounds can suggest unhelpful fixes (T: 'a instead of Self: 'a), but the next compiler error after making the suggested change is helpful.

We can end up requiring that for<'a> I: 'a when we really want for<'a where I: 'a> I: 'a. This can leave unhelpful errors than effectively can't be satisfied unless I: 'static. Requires bigger changes and not only GATs.

Unlike with non-generic associated types, we don't eagerly normalize with param env candidates. This is intended behavior (for now), to avoid accidentaly stabilizing picking arbitrary impls.

Some Iterator adapter patterns (namely filter) require Polonius or unsafe to work.

Potential Future work

Universal type/const quantification

No work has been done to implement this. There are also some questions around implied bounds.

Object-safe GATs

The intention is to make traits with GATs object-safe. There are some design work to be done around well-formedness rules and general implementation.

GATified std lib types

It would be helpful to either introduce new std lib traits (like LendingIterator) or to modify existing ones (adding a 'a generic to Iterator::Item). There also a number of other candidates, like Index/IndexMut and Fn/FnMut/FnOnce.

Reduce the need for for<'a>

Seen here. One possible syntax:

trait Iterable {
    type Iter<'a>: Iterator<Item = Self::Item<'a>>;
}

fn foo<T>() where T: Iterable, T::Item<let 'a>: Display { } //note the `let`!

Better implied bounds on higher-ranked things

Currently if we have a type Item<'a> where self: 'a, and a for<'a> T: Iterator<Item<'a> = &'a (), this requires for<'a> Self: 'a. Really, we want for<'a where T: 'a> ...

There was some mentions of this all the back in the RFC thread here.

Alternatives

Make generics on associated type in bounds a binder

Imagine the bound for<'a> T: Trait<Item<'a>= &'a ()>. It might be that for<'a> is "too large" and it should instead be T: Trait<for<'a> Item<'a>= &'a ()>. Brought up in RFC thread here and in a few places since.

Another related question: Is for<'a> the right syntax? Maybe where<'a>? Also originally found in RFC thread here.

Stabilize lifetime GATs first

This has been brought up a few times. The idea is to only allow GATs with lifetime parameters to in initial stabilization. This was probably most useful prior to actual implementation. At this point, lifetimes, types, and consts are all implemented and work. It feels like an arbitrary split without strong reason.

History

@rustbot rustbot added T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-rustdoc Relevant to the rustdoc team, which will review and decide on the PR/issue. labels May 4, 2022
@rust-highfive
Copy link
Collaborator

Some changes occurred in src/tools/rustfmt.

cc @rust-lang/rustfmt

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label May 4, 2022
@c410-f3r
Copy link
Contributor

c410-f3r commented May 4, 2022

Thank you @jackh726. Like, really, thank you very much!

@jackh726 jackh726 added T-lang Relevant to the language team, which will review and decide on the PR/issue. and removed T-rustdoc Relevant to the rustdoc team, which will review and decide on the PR/issue. labels May 4, 2022
@bors
Copy link
Contributor

bors commented May 5, 2022

☔ The latest upstream changes (presumably #96593) made this pull request unmergeable. Please resolve the merge conflicts.

@nrc
Copy link
Member

nrc commented May 5, 2022

I don't think we should stabilise GATs now (and I'm not convinced we should stabilise GATs at all). I'm sorry this is going to be a negative post, I'll try and be as positive as possible. I really appreciate the work that has gone into this feature and for help in answering my questions along the way.

I think it is very important to make a strong argument that not only can we add GATs to the language, but that we should. This is an important decision. GATs are probably the largest change to the type systems since associated types (pre 1.0), and certainly the largest change since 1.0. They're also the largest addition in complexity since way before 1.0 and the language feature with the largest possibility to change the character of the language since before 1.0.

I think that a decision on GATs is a fairly straightforward trade-off between complexity and expressivity. I'll address both sides of the trade-off.

Complexity

Rust is often criticised for being an overly complex language. From last year's annual survey, 33% of users said their biggest worry for the future of Rust was that it would become "too complex" (the second highest ranked answer). Languages which support GATs, HKTs, or similar typically have even worse reputations than Rust for complexity and learning curve.

GATs nearly always increase complexity. They introduce new syntax and new semantics to the type system. There are a few use cases for lifetime-GATs which simplify types, but mostly GATs are useful for expressing new abstractions which are inherently difficult for many programmers to understand.

GATs' complexity is not a 'zero-cost abstraction' (in the sense that you only pay the price for a feature if you use it): GATs will primarily be used by library authors and are part of the API, thus programmers will not get a choice to avoid them. They will be part of libraries and if programmers want to use those libraries, they must learn about GATs (compare to async, where if a programmer is not doing async programming, they don't need to know about async or await).

GATs' complexity is not restricted to advanced programmers. Since GATs are used in APIs, they cannot be hidden only in implementations where only advanced programmers (as library authors are likely to be) need to care about them. They are exposed to all programmers (compare to unsafe coding features, which can be completely encapsulated).

GATs are a feature which appeal to language geeks and compiler hackers and can even be fairly intuitive to us, but which are terrifying for most programmers. Note how popular Haskell is with PL/compiler people, but how it is largely shunned by industry.

Expressivity

GATs clearly increase expressivity, but I think that a solid argument that the expressivity is useful has not been made. Furthermore, the increased expressivity changes the character of Rust significantly.

There are use cases for GATs, but very few of them have been proved out. I don't know of any use cases which have been implemented and demonstrated to be significantly useful. I realise there is a reluctance to use unstable features, and this is a high bar. But the bar should be high - this is a huge change to the language.

There are numerous cases of small bugs or small gaps in expressivity which have prevented people using GATs for the use cases they want to use them for (see e.g., the blog post linked from the OP, or this reddit thread). These are the sort of things which must be addressed before stabilisation so that we can be sure that they are in fact small and not hiding insurmountable issues.

GATs have a strong use case inside the compiler as part of the implementation of async methods or impl Trait in traits. However, there is no requirement that GATs need to be exposed to the user to facilitate this usage. I think using GATs as a principled internal representation is fantastic, but that does not require exposing them to users.

Most use cases (and certainly most of the compelling use cases) are for lifetime GATs. I think that we must separately justify lifetime and type GATs, and we could add one to the language without adding the other (e.g., we have HRTBs for lifetimes but not for types). Note also that lifetime GATs are categorically simpler than type GATs because the grammar of types is structural and recursive, whereas the grammar of lifetimes is simple. Furthermore, lifetimes are part of what makes Rust unique and thus expressivity at the cost of complexity is more essential to the language in the lifetime case.

Finally, GATs increase expressivity but in a way which takes Rust in a new direction. GATs and HKTs more generally are a fine way of building abstractions, but they are not the Rust way. We use concrete types like Option and Result rather than monads, we use borrowed references to abstract over storage rather than an abstract pointer type, and we have found many times that abstractions like 'collection' or 'number' are not good fits for most libraries. There is probably a fine language which is something like Rust with abstractions built around HKTs and similar type system ideas, but that language is not Rust. It would require a different standard library, different ergonomics, and different programming idioms.

@zesterer
Copy link
Contributor

zesterer commented May 5, 2022

@nrc I agree with the sentiment of much of your post, but disagree on the specifics. Rather, I think the problem of teaching and complexity is a pervasive problem across Rust as a whole, and I don't think that holding back features is a particularly effective way to solve the problem.

As-is, I've seen many APIs in the wild that are unnecessarily complex because the author didn't have access to GATs, requiring them to create absurd abstraction towers like custom type family traits in order to achieve similar expressivity. GATs don't really enable any capability that didn't exist before, but they definitely have the power to simplify many of the more weird cases of type astronomy.

I don't know of any use cases which have been implemented and demonstrated to be significantly useful.

To this, I'd like to provide a specific and solid example of GATs (and in particular, type GATs) proving extremely useful.

I work on chumsky, a parser combinator crate. I've recently been experimenting with GATs internally as a way to control exactly what code Rust generates. Instead of praying to the LLVM gods that the compiler might optimise things, I use a GAT to project a particular parsing 'strategy' into the implementation of parsers. I've found that I can significantly improve the performance of the library by an order of magnitude, even beating out hand-written parsers, nom, and serde_json (with several caveats) without harming the library's expressivity (and, in fact, improving it). This all happens without the GATs themselves being exposed to library users at all.

I strongly suspect that many similar use-cases will appear in the future, given the possibilities GATs open up to specialise the implementation of functions, predicated upon a generic type. As far as I'm aware, no other languages that support GATs (or adjacent features like HKTs) have the same monomorphisation and performance promises that Rust has, so this space remains mostly unexplored. But, without stabilisation, this space is not open for exploration by API authors that care about performance.

@NobodyXu
Copy link
Contributor

NobodyXu commented May 5, 2022

@zesterer Can you please explain on how you use GAT to speedup your parser?

@zesterer
Copy link
Contributor

zesterer commented May 5, 2022

@NobodyXu

Parser combinators are parsers composed of smaller parsers, similar to how Iterator chains are created compositionally using generic types like std::iter::Map<T, F>. You might have a parser for a pattern named a, and another named b, and desire to create a new parser that parses one pattern and then the other: a.then(b).

There are a number of cases where it's necessary to parse a pattern without actually evaluating its output. For example, a.separated_by(b) (which parses a b a b a ...) produces a Vec<output of a>, but the output of b goes unused. Currently, the library evaluates the outputs of both a and b but discards the latter.

This can be very wasteful though, particularly if the creation of b's output requires allocation, such as in the case of ident.separated_by(whitespace.repeated()) (we end up with many discarded Vec<char>s).

With GATs, we can specialise the invocation of each parser's parse function with a type that controls whether an output value gets actually generated or not at compile-time (in effect, a restricted form of the monad pattern) that uses a GAT to work across whatever types the implementation of the parser cares for.

The beautiful part is that this has no impact on the user-facing API, but 'magically' speeds up the parser by statically guaranteeing that unnecessary work will be skipped, allowing something like a.repeated() to only allocate if it's used in a context where the output value is actually needed.

Although the details of this case are quite specific to chumsky, I believe this general pattern - GATs as a way to generically define an operation with types known only to the implementation - is generally useful for a lot of code, as is visible in existing languages with GATs/HKTs. Where Rust really hits the ball out of the park is that it guarantees monomorphisation, allowing these patterns to be truly zero-cost. It's difficult to overstate just how powerful that is.

@tinaun
Copy link
Contributor

tinaun commented May 5, 2022

wild alternative number 3: only stabilize type GATs

while I don't know how possible this is in practice, (since Item is a supertype of Item<&'a T> after all) but to me it seems like the vast majority of the footguns and undesired complexity that can make dealing with gats suprisingly unergonomic is around LendingIterator and friends, but not around "simple" generic types like in zesterer's example.

At the very least, moving the marketing of this feature away from LendingIterator and lifetime GATs in general and towards concrete ergonomic and performance wins with "simple types" in the short term could be useful in reducing complexity for the end user to understand

@BurntSushi
Copy link
Member

I think I might be one of the people who has wanted GATs the longest. My desire for them predates my lame attempt at working around their absence many moons ago. The first time I realized the Iterator trait was insufficient for what I wanted was before Rust 1.0 in 2014 when I wrote one of the first versions of the csv crate. All I wanted to do was write an iterator that lent out a borrow of an internal buffer in order to avoid allocating a new record on each iteration. So it's been... about eight years of waiting for me. :-) I am very appreciative of all the work @jackh726 that you've done on this! It looks absolutely amazing and it's really cool to see Rust code making use of GATs and working.

With all that said, I do unfortunately tend to agree with quite a bit of what @nrc is saying.

There are numerous cases of small bugs or small gaps in expressivity which have prevented people using GATs for the use cases they want to use them for (see e.g., the blog post linked from the OP, or this reddit thread). These are the sort of things which must be addressed before stabilisation so that we can be sure that they are in fact small and not hiding insurmountable issues.

I'm quite sympathetic to GATs exploding the complexity budget of Rust, but I think this is one of the more compelling points in terms of not stabilizing them as-is. I understand the idea of making incremental progress, but as a total outsider to lang development, this feature looks like it has way too many papercuts and limitations to stabilize right now. Not being able to write a basic filter implementation on a LendingIterator trait in safe code is a huge red flag to me. Namely, if I can't write a filter adaptor in safe straight-forward code, then... what else can't I do? Add on to that the lack of dyn traits and the inability to implement other types of adaptors like a WindowsMut, and it feels like there is just too much that can't be done. What's worse, I don't even know how to articulate in a sentence or two what GATs can or can't be used for if they're stabilized as-is.

In terms of making incremental progress, what does it look like to use GATs as an implementation detail to make async traits work? Is there a reason why we shouldn't start there?

I do appreciate that there is a chicken-and-egg problem here. It's hard to get the experience I think we need with people actually using GATs before stabilizing them. I'm not sure how to approach that problem other than taking a more conservative incremental approach here.

@jam1garner
Copy link
Contributor

jam1garner commented May 5, 2022

In response to @nrc (and making every section collapsible to not take up 2 vertical screen widths):

"GATs nearly always increase complexity"

GATs nearly always increase complexity. They introduce new syntax and new semantics to the type system.

I've always found this argument weird. To me GATs is the perfect example of a "simplification" feature. This is not because GATs are easy to reason about, but rather because I see the lack of them as an inconsistency in the language in its present state.

If I have the following code:

type Result = core::result::Result<(), Error>;

trait Iterator {
    type Item;
}

To me, there is a very intentional parallel here, as ultimately an associated type is just a type alias with a specific purpose. If you really boil them down, sure, the late-binding nature of type aliases make them function a bit differently. But at the end of the day, an associated type is really just an eagerly type-checked type-alias as a part of a trait's interface.

So to me the following:

type Result<T> = core::result::Result<T, Error>;

trait Iterator {
    type Item<'a>;
}

Is the same feature just more consistent between the kinds of type-alias. This is possibly an unfair comparison due to the fact one has eager trait bounds and the other is evaluated at use-time, and thus has different type-system vibes. But honestly, the first time I tried to add a generic bound to an associated type years ago, I remember my experience was:

  1. "wait that doesn't work?"
  2. struggling to find the right keywords to google
  3. "wait THAT'S what people mean by GATs? I thought it was some wizardry"

To me, this is what separates it from non-lifetime HRTBs: it is a natural extension of existing ideas, not a true introduction of genericism over an additional dimension.

GATs' complexity is in the public API

GATs' complexity is not restricted to advanced programmers. Since GATs are used in APIs, they cannot be hidden only in implementations where only advanced programmers (as library authors are likely to be) need to care about them

Honestly not sure I agree with this. For example serde's Deserialize trait:

trait Deserialize<'de>: Sized {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>;
}

This uses: trait-level lifetime generics, associated types, a generic type, and a method-level where clause which ties a trait-level generic lifetime as a generic parameter in a trait bound.

The thing is... I've seen countless new users not only use serde but enjoy it quite a bit, far before they can even parse the syntax of the above. Because honestly? you don't need to understand 90% of a generic API, especially if it is well-documented. A beginner only needs to care about one thing: "does my type implement Deserialize"?

serde couldn't exist in its current form without the decently complex capabilities present above, and I highly doubt anyone is arguing serde is an example of Rust being too complex or overly academic. Rust's Iterator APIs are arguably just as bad with their use of Fn traits, associated types with trait bounds, wrapper types, etc. But in practice, do people have much trouble with them? or even think about the generic bounds much if they aren't writing their own combinator or highly-generic code? I don't personally, and I feel I haven't seen this behavior from any of the beginners I've regularly worked with.

In my experience, the reality is these powerful features only end up "in the APIs" in a stability sense. Users just don't need to care about them until things break. And when things break, rustc solves for why and gives excellent diagnostics. It tells you "Deserialize isn't implemented" and then you never cared about the specifics of the trait bounds, for even a second. (albeit I would definitely say work in allowing library authors to provide domain-specific insight would make this so much better)

I would go as far as to say this makes things easier on the end user, not harder.

"if a programmer is not doing async programming, they don't need to know about async or await"

GATs' complexity is not a 'zero-cost abstraction' (in the sense that you only pay the price for a feature if you use it): GATs will primarily be used by library authors and are part of the API, thus programmers will not get a choice to avoid them. They will be part of libraries and if programmers want to use those libraries, they must learn about GATs (compare to async, where if a programmer is not doing async programming, they don't need to know about async or await).

I genuinely hate to be the person to point this out but... isn't async a prime example of the opposite? There's definitely been a non-zero amount of the ecosystem that falls into the category of "may be used in async or synchronous contexts". And since there exists no good way (yet) to provide an API that can be synchronous or asynchronous, a good portion of those libraries just expose an async-only API (Think crates for interacting with web APIs). It's not super pervasive, but I'd need more than one hand to count the number of projects where I've had to introduce async unnecessarily. I honestly personally think this aids your point, but I figured it was pointing out regardless.

"Haskell is [...] largely shunned by industry"

Note how popular Haskell is with PL/compiler people, but how it is largely shunned by industry.

I think it's far from fair to try and imply a single aspect of Haskell (the degree of abstraction) is the sole cause. Honestly it would be incredibly impressive if Haskell was more popular given everything about it. It's functional, its syntax is far from any language that already has a foothold in industry, it doesn't really have a "killer feature" (its primary feature is just its core language design as a general-purpose language), it doesn't have a specific niche it's the go-to for. Consider a language which is quite popular in industry, and about as far as possible from Haskell: Go. It has a niche (web services/backend/devops/etc), it has a "killer feature" (goroutines), it has a corporate backing (Google), it is abundantly uninterested in introducing anything but the most mainstream of language/syntax design, etc. All of these are important factors for adoption! As is being a simple language is likely a big factor too of course, but that's my point: Go's success doesn't exist in a vacuum of its simplicity, and Haskell's lack of industry permeation and ability to form complex abstractions doesn't exist in a vacuum either.

I would say, in a lot of ways, Rust has brought in a lot of ideas that (at least at the time of their introduction to the language) would fall into the "only exists in 'academic' languages". Things like immutability, type inference, sum types, pattern matching, typeclasses, etc are all feature where Rust has had great success taking great ideas from less-successful languages and tried to fit them into a design that is more familiar and . The joke "Rust is a gateway drug to Haskell" is, imo, one of Rust's biggest compliments, as to me it speaks to Rust's ability to bring some of the best parts of PL design to a more accessible language with a design that enables a higher degree of practical usage (namely feature like FFI and flexibility regarding where it can be used).


Now for the parts where I agree with you, because while I don't agree with your conclusion, I absolutely found every point you raised to be well-constructed and at minimum worth consideration.

"abstractions like 'collection' [...] are not good fits for most"

we have found many times that abstractions like 'collection' or 'number' are not good fits for most libraries

100% agreed. Recent discussions have seen quite a bit of agreement that both of those are too general (albeit I would say large subsets of the idea of "number" are useful depending on the context). I would say especially there doesn't seem to be many experienced Rust users in favor of collection traits/abstractions. I would actually hate to see that, frankly. However, just because GATs would enable this style of abstraction more doesn't mean we have to make that idiomatic.

And you might be asking why I'd say to add a feature but not use it, as frankly I imagine that is what it reads like. Personally I don't really think this is where GATs shine? And that leads right into my next point....

"Most use cases [...] are for lifetime GATs"

Most use cases (and certainly most of the compelling use cases) are for lifetime GATs

You are.... completely right here. I have occasionally needed type GATs here and there, but I feel very limited by the lack of lifetime bounds. Whether your conclusion (stabilize type GATs separately) is the direction we should go, I have less of an opinion on.

I think for a bit of context for those who aren't familiar, or a concrete example for those who are, if we have a trait like such:

trait CanDoThing {
    type Input;
    
    fn do_thing(&self, input: Self::Input);
}

We have a bit of a fundamental limitation: there is no way for us to tie the lifetime of Input to the length of do_thing. So despite the fact do_thing might even guarantee the input isn't long-lived, it isn't possible to specify that such that a user can even declare a borrowed Input type. The solution with GATs is just to give Input a lifetime and then we can specify that Input doesn't live past the end of do_thing:

trait CanDoThing {
    type Input<'a>;
    
    fn do_thing(&self, input: Self::Input<'_>);
}

One reason this is really useful is allowing composable traits (think serde: if you can serialize all the fields, you can serialize the struct) to have user-provided types without requiring either reference counting or passing by value. For declarative APIs (serde, clap/structopt, etc) this is a really useful feature. It's technically possible to emulate lifetime GATs here using HRTB trickery to make a lifetime bound out of thin air, but it has significant limitations and is prohibitively messy.


In the end though, I think at minimum we should really be stabilizing lifetime GATs. I personally am not too worried about type GATs, however I think this cost/benefit of holding them back is far more agreeable than lifetime GATs. So even if the route is "don't stabilize everything" out of an abundance of caution, I would still heavily implore those making the final call to at minimum shoot for lifetime GATs being stabilized this cycle.


edits

Response to BurntSushi

Also I would definitely say I agree with @BurntSushi that the most compelling reason for me as to why we might hold back GATs isn't complexity or anything, it's "there are papercuts and I am not in the loop enough to assert the fixes would be backwards-compatible". My impression is that improvements would be backwards compatible by allowing trait bounds that currently aren't expressible to be opted into (allowing for constraining lifetimes in such a manner that trait bounds can be expressed in the places that currently cause issues). To me, this feels more like const generics: sure, a lot of things can't be expressed yet, they just need more work. So in a sense, I guess it's more of a matter of having confidence in that being the case? Are there papercuts which allowing upper bound constraints on HRTB lifetimes (relevant snippet from Sabrina's blog post below) don't cover?

The ultimate conclusion of all this is that HRTBs basically can’t be used with lifetime GATs at all. for<'a> just doesn’t express the right requirement — we don’t want to require the bound for any lifetime, we only really want to require it for lifetimes shorter than '0. Ideally, we would be able to write in a where clause there, so the bounds of print_items could become:

fn print_items<I>(mut iter: I)
where
	I: LendingIterator,
	for<'a where I: 'a> I::Item<'a>: Debug,

Also this part from @zesterer describes my needs quite nicely:

Although the details of this case are quite specific to chumsky, I believe this general pattern - GATs as a way to generically define an operation with types known only to the implementation - is generally useful for a lot of code,

This is a pattern that I find myself stubbing my toe on quite often (as someone who writes a lot of libraries focused on ease-of-use, not as a normal user). Very well put.

@hadronized
Copy link

hadronized commented May 5, 2022

I genuinely hate to be the person to point this out but... isn't async a prime example of the opposite? There's definitely been a non-zero amount of the ecosystem that falls into the category of "may be used in async or synchronous contexts".

Yeah, I wanted to say that too. A function such as:

async foo() -> i32

is not a zero-cost abstraction either, both in terms of implementation and cognitive complexity (someone who doesn’t know async programming in Rust would assume the function returns i32 while it doesn’t — it, in fact, returns impl Future<Output = i32>).

So GAT requires learning… like pretty much everything a typed language provides. Making a point on the tradeoffs here using something that is already a pretty big tradeoff (and distracting, because the types are harder to read with async) is a bit offsetting to me.


As-is, I've seen many APIs in the wild that are unnecessarily complex because the author didn't have access to GATs, requiring them to create absurd abstraction towers like custom type family traits in order to achieve similar expressivity. GATs don't really enable any capability that didn't exist before, but they definitely have the power to simplify many of the more weird cases of type astronomy.

Yeah, I struggle a lot with that in luminance. It requires creating bazillions of traits. The problem is the same with pretty much anything else that would fall into @nrc comment about complexity (HKT, rank-2 types, etc.), which have valid use cases in lots of libraries (luminance is a ecosystem I maintain so I can talk about it, but I also have issues with EDSLs for instance).

@Stargateur
Copy link
Contributor

Stargateur commented May 5, 2022

@phaazon "is not a zero-cost abstraction" zero cost abstraction doesn't mean zero cost, it's mean "you would have the same result doing it by "hand"". So you use here is wrong, async being zero cost abstraction mean that if you want do the same feature that offer async by hand you would not gain speed. (thus I don't talk about implementation here) What does 'Zero Cost Abstraction' mean?

@BurntSushi
Copy link
Member

BurntSushi commented May 5, 2022

@jam1garner

Also I would definitely say I agree with @BurntSushi that the most compelling reason for me as to why we might hold back GATs isn't complexity or anything

I am also quite sympathetic to the complexity argument here too. And I think I see the complexity as very different than you, based on what you've written. That GATs make the language more consistent in some corners does not really hold much sway with me in terms of "complexity" here. What I think of, and what I think @nrc is thinking, is the emergent complexity of abstractions that are enabled by GATs. I don't really know how to untie the knot here in terms of teasing these different sorts of complexities apart, but they are very different ideas that unfortunately can both be reasonably described as "complexity." If someone wanted to start a Zulip stream on this topic, I'd be happy to try and explain this viewpoint a bit more because I think it's a huge topic that has the potential to derail this thread.

Are there papercuts which allowing upper bound constraints on HRTB lifetimes (relevant snippet from Sabrina's blog post below) don't cover?

I have absolutely no idea. I have no tools for how to even think about an answer to this question. :-/

My impression is that improvements would be backwards compatible by allowing trait bounds that currently aren't expressible to be opted into (allowing for constraining lifetimes in such a manner that trait bounds can be expressed in the places that currently cause issues). To me, this feels more like const generics: sure, a lot of things can't be expressed yet, they just need more work. So in a sense, I guess it's more of a matter of having confidence in that being the case?

OK so thank you for mentioning this, because this is not what I had in mind. If everyone involved in Rust's type system really believes that all outstanding issues are not only resolvable, but are resolvable in a backwards compatible way, then I'm happy to trust that. They're the experts, not me. I might raise an eyebrow in surprise of such confidence given how many outstanding issues there are today personally, but no, ultimately I'd trust them.

The issue I have with GAT's incompleteness is actually about the user experience. The const analogy is a good one, because it demonstrates just how different things are here. In my understanding, it is very easy to articulate and apply the limitations of what is allowed in a const context. Just about anyone can understand things like:

  • You can't allocate in a const context.
  • You can't write loops in a const context. (No longer a restriction.)
  • You can't call non-const functions in a const context.
  • etc...

Even better than that, the limitations are so clear and crisp, that the compiler can recognize it and tell you exactly what's wrong. That's a reasonable failure mode. "They just haven't gotten to it today so I can't do that." OK. Great. Now I can move on to some other solution to my problem or whatever.

Compare that with trying to write a Filter adaptor for a LendingIterator trait. What error do you get? Does it tell you that "GATs don't currently support blah blah, so you can't express this pattern yet." No, it doesn't. Of course, maybe this is just an artifact of not having better error messages yet. But is it? From the corresponding issue, it looks like the actual problem is a limitation in the borrow checker that GATs now make easier to stumble across... So it's not necessarily easily expressible as a function of what GATs let you express, but has something more to do with how different language features intersect.

This is a really important point. Because if I try to write that Filter adaptor and get an error like that, what do you think is going to happen? Do you think I'm going to immediately give up and understand that GATs just aren't complete yet? Heck no I'm not. I'm going to keep trying to re-arrange the code because I'm pretty sure what I'm trying to do is legal. So maybe there's a contortion I can make to the code to make it work. All in all, it's an extremely frustrating experience. Now in this case, I might get lucky and search and find the Filter issue and discover that what I'm trying to do isn't possible yet. At which point, I can give up or use the work-around. But are all such limitations with GATs so easily discoverable? I don't know the answer to that question, but nothing I've seen written by anyone leads me to believe that anyone knows the answer to that question, even the experts. But maybe I'm wrong.

My advice to folks is to try and look at this feature and the experience it gives to users through beginner eyes. Heck, I'm no Rust beginner (nor even someone completely ignorant of type theory) and a lot of the failure modes I've seen with the GAT feature are really inscrutable. I think we should do one of three things:

  • Build a smoother onboarding experience (better error messages and understandability). Something akin to the grokability of what is or isn't allowed in a const context today would be great and would really assuage a lot of my concerns here.
  • Drastically decrease the number of failure modes. It's one thing to try and do something sophisticated with the type system and run into an inscrutable error message that doesn't help much. But it's another to do something reasonably basic that potentially many folks will run into and provide an unclear error message. So if you can smooth out the feature such that most interactions with it work like you'd expect, then you lessen the pressure to improve on failure modes.
  • Sidestep the above two problems by starting with a more conservative stabilization. (I am unclear on the parameters of what's possible or feasible here.)

@Stargateur
Copy link
Contributor

Stargateur commented May 5, 2022

This is a really important point. Because if I try to write that Filter adaptor and get an error like that, what do you think is going to happen? Do you think I'm going to immediately give up and understand that GATs just aren't complete yet? Heck no I'm not. I'm going to keep trying to re-arrange the code because I'm pretty sure what I'm trying to do is legal. So maybe there's a contortion I can make to the code to make it work. All in all, it's an extremely frustrating experience. Now in this case, I might get lucky and search and find the Filter issue and discover that what I'm trying to do isn't possible yet. At which point, I can give up or use the work-around. But are all such limitations with GATs so easily discoverable? I don't know the answer to that question, but nothing I've seen written by anyone leads me to believe that anyone knows the answer to that question, even the experts. But maybe I'm wrong.

@BurntSushi The problem of lifetime borrow that the filter example show is not unique to GaTs, and I saw a lot of question on stackoverflow about this problem. Always solve by polonius borrow checker. I don't think this is a good argument on this case, it apply to every Rust code. But you are right everytime the user is very confuse why the code don't compile. Returning a reference from a HashMap or Vec causes a borrow to last beyond the scope it's in? for example (there are several linked question). Notice the question is from 2016

@BurntSushi
Copy link
Member

@Stargateur That's beside the point for a few reasons, and I don't think is really addressing the substance of my concerns.

Firstly I acknowledged that the Filter adaptor example was about the interaction of language features, so I know it's a pre-existing issue. Namely, if GATs make that borrow checker limitation more common than it already is, that isn't good. My argument is not one of mere existence. It's about the overall user experience. The work-around for this particular Filter adaptor example is also not nearly as nice as the work-arounds for the SO questions you've linked.

Secondly, the Filter adaptor code is an example, not an argument. It exemplifies how difficult it is to know when you've hit a limitation of a new language feature, or a limitation of the interaction of language features or whatever and whether you should keep trying to change your code to get the compiler to accept it. How many hours went into that blog post trying to figure out not just how to write a WindowsMut adaptor, but whether it was even possible in the first place? How many other permutations of this problem exist? I have no clue.

@jam1garner
Copy link
Contributor

@BurntSushi very fair points! And apologies on the phrasing, didn't draw enough of a line between where I was riffing off you and where I was stating my own opinion.

While I'm sure this isn't the greatest solution, I wonder if something like "GATs without trait bounds" is a possible path forward? (Or maybe slightly broader—no constraining lifetime GATs with trait bounds?)

I know for many that's functionally useless, it just happens to cover roughly a third of my usecases while (to my knowledge) having a good biglt fewer edge cases for {borrow, type, trait bound} checking to go wrong. Not sure how representative my usecase is though so maybe for everyone else that'd be more frustrating than anything :)

And you're right I definitely was (somewhat intentionally) construing different definitions (feature cognitive overhead vs, as you said, emergent complexity of the abstractions it enables). I'd like to think I somewhat covered how I feel about the kind you/nrc are talking about (and how I think in practice I'm not sure beginners would actually need to think about it, similar to iterator adapter trait bounds, assuming "collection traits" and the likes don't become idiomatic), however that is of course a bit handwavey as my argument isn't all that concrete.

And yeah the UX could be better :( I can't speak for everyone but as someone who tries to make diagnostics PRs when I hit issues and have the time/energy, I personally can't really kick the tires if I can't use the feature in my libraries, and thus don't have a very natural path to find pain points to. I've converted a library of mine twice (once to real GATs, once to lifetime GATs emulation via HRTBs) but it's a lot of refactoring effort (big crate, w/ derive macro, etc) only to not get too far out of trivial usage territory.

Part of me is tempted to say "if diagnostics are the blocker, stabilize and improvements will roll in", as I believe someone else said upthread it's sorta a chicken-and-egg issue. Since it's mainly a feature aimed at sufficiently complex libraries I don't think my situation (prohibitively high cost to give any real-world testing, only for branch staling to make that effort difficult to keep useful) is that uncommon.

Regardless, I'm very grateful for the work being done here. The boundaries are fuzzy and the interactions with lifetimes are pretty novel. Even if GATs get held back indefinitely I'll be happy, whatever decision is made will probably be the one I agree with in 2 year's time anyways :P

@slanterns
Copy link
Contributor

slanterns commented May 5, 2022

it's far from fair to try and imply a single aspect of Haskell (the degree of abstraction) is the sole cause...

I think it's far from fair to try and imply a single aspect of Haskell (the degree of abstraction) is the sole cause. Honestly it would be incredibly impressive if Haskell was more popular given everything about it. It's functional, its syntax is far from any language that already has a foothold in industry, it doesn't really have a "killer feature" (its primary feature is just its core language design as a general-purpose language), it doesn't have a specific niche it's the go-to for. Consider a language which is quite popular in industry, and about as far as possible from Haskell: Go. It has a niche (web services/backend/devops/etc), it has a "killer feature" (goroutines), it has a corporate backing (Google), it is abundantly uninterested in introducing anything but the most mainstream of language/syntax design, etc. All of these are important factors for adoption! As is being a simple language is likely a big factor too of course, but that's my point: Go's success doesn't exist in a vacuum of its simplicity, and Haskell's lack of industry permeation and ability to form complex abstractions doesn't exist in a vacuum either.

I would say, in a lot of ways, Rust has brought in a lot of ideas that (at least at the time of their introduction to the language) would fall into the "only exists in 'academic' languages". Things like immutability, type inference, sum types, pattern matching, typeclasses, etc are all feature where Rust has had great success taking great ideas from less-successful languages and tried to fit them into a design that is more familiar and . The joke "Rust is a gateway drug to Haskell" is, imo, one of Rust's biggest compliments, as to me it speaks to Rust's ability to bring some of the best parts of PL design to a more accessible language with a design that enables a higher degree of practical usage (namely feature like FFI and flexibility regarding where it can be used).

I agree with that. We cannot simply attribute it to HKT, and HKT is rather intuitive (especially in Haskell) for me. The imagination of HKT in the blog post looks even attractive since it seems actually a simplification for me. Anyway, thanks everyone for pushing Rust into a better language!

@CraftSpider
Copy link
Contributor

I agree with the statement that HKTs are not, on their own, fundamentally unintuitive. Also, as an outside observer: why are these points being raised now, and not before many posts (including official Rust blog posts) about GATs approaching stabilization? I've been expecting them as a user as a non-controversial addition for a while now. This isn't meant as a criticism of the points, just curiosity about what happened that they only came up after the work was done.

@Stargateur
Copy link
Contributor

Stargateur commented May 6, 2022

@CraftSpider Theoretically, we already decide to include GaTs like describe in RFC 1598, thus it's allowed to make small change or even cancel everything before it's land to stable. "why are these points being raised now" there two factors here, first it's very hard to predict how thing as complex as GaTs will go. Unexpected things happen. Secondly, the stabilisation request is the "last time" where people can raise concern. Specially the concern here is precisely about the stabilisation could be premature. It's not surprising that a feature like GaTs that could change everything in Rust attract concern like this.

Then these concerns can be look by the team associate with the stabilisation, here I think it's the Lang team that will have the last word about this.

@nrc
Copy link
Member

nrc commented May 6, 2022

I'll address some technical points later, but for now...

Also, as an outside observer: why are these points being raised now, and not before many posts (including official Rust blog posts) about GATs approaching stabilization? I've been expecting them as a user as a non-controversial addition for a while now. This isn't meant as a criticism of the points, just curiosity about what happened that they only came up after the work was done.

This is partly a process failure, but also this is exceptional work both in scope, and in time between RFC and implementation (it's been 6 years since the RFC was proposed and 5 years since it was accepted). In our process for creating new features, there is no formal place for registering objections between RFC discussion and stabilisation discussion, that is usually OK, but here the time period was exceptionally long. Personally, I have registered concerns about this feature privately and publicly, but like I say there is nowhere to do that officially. The project has changed a lot since the RFC was proposed, both technically (the type system has got more complex in other ways) and non-technically (the language is much more mature now, we have many more users, and are attracting new users at a much higher rate), also people change - in six years people leave and new people arrive, and people's opinions change.

Also, it is expected that during implementation and once an implementation is available for use, we gain experience and that informs our decision making and can change people's opinions. Accepting an RFC is never a guarantee that a feature will be stabilized, and in fact it is quite rare that a feature is stabilised in exactly the same form as described in an RFC.

@kellerkindt
Copy link
Contributor

kellerkindt commented May 6, 2022

As an outside observer: Having so many points raised against the stabilization while trying to stabilize this is a bit concerning. The main take-away I get from scrolling through this thread is that GATs are not ready yet (missing/limiting parts) or that it's actually unknown whether GATs in this form is production ready / the papercuts and limitations will have bad/limiting consequences in the future (missing experience).

I totally get the chicken-and-egg problem here.

Extending @BurntSushi idea to "Sidestep the above two problems by starting with a more conservative stabilization": I remember the async stabilization being driven and motivated very much by (positive) experience from nightly production usage (fuchsia team). Maybe something like that is needed for GATs as well to assess that the current approach is ready. Don't the generators for async fns in (static) traits need this feature? Maybe implementing those and therefore using GATs but (for now) only internally in the compiler will provide enough experience to confidently stabilize or adjust this feature? Then again, as an "outside observer", I don't know if this is the case already.

As a closing note, I am really baffled by the commitment of all involved. For me personally GATs are first of all the "things needed" to get async fns in traits and I am regularly surprised of how much work this actually requires.

Hats off to you folks!

@PoignardAzur
Copy link
Contributor

Also, as an outside observer: why are these points being raised now, and not before many posts (including official Rust blog posts) about GATs approaching stabilization?

My personal take: at first I thought positively of GATs, and my reaction was essentially "I'm looking forwards to the day this is available with stable Rust".

Then as we got closer to the deadline, more and more blog posts came out with examples of GAT code and at that point my reaction became "Wait, is that what GATs look like in practice? I don't understand any of this code. Am I going to have to read code like this everywhere soon?", hence why I'd now agree with BurntSushi that the feature still needs some baking.

For instance, reading code like this is making me very nervous:

pub trait LendingIteratorLifetime<'this>
where
	Self: 'this,
{
	type Item;
}

pub trait LendingIterator: for<'this> LendingIteratorLifetime<'this> {
	fn next(&mut self) -> Option<<Self as LendingIteratorLifetime<'_>>::Item>;
}

@zesterer
Copy link
Contributor

zesterer commented May 6, 2022

For instance, reading code like this is making me very nervous:

That's not GAT code, that's code that's required to emulate GATs in Rust today.

Incidentally, the very specific thing mentioned in the post (filter on LendingIterator) cannot yet be constructed with GATs (pending improvements to HRTBs, a feature broadly orthogonal to GATs), but in general this is a good example of the way GATs actually simplify a lot of trait-heavy code.

@rust-timer
Copy link
Collaborator

Finished benchmarking commit (7098c18): comparison URL.

Overall result: no relevant changes - no action needed

@rustbot label: -perf-regression

Instruction count

This benchmark run did not return any relevant results for this metric.

Max RSS (memory usage)

Results

This is a less reliable metric that may be of interest but was not used to determine the overall result at the top of this comment.

mean1 range count2
Regressions ❌
(primary)
5.4% [5.4%, 5.4%] 1
Regressions ❌
(secondary)
1.8% [1.8%, 1.8%] 1
Improvements ✅
(primary)
-2.6% [-2.6%, -2.6%] 1
Improvements ✅
(secondary)
-2.3% [-2.7%, -1.6%] 3
All ❌✅ (primary) 1.4% [-2.6%, 5.4%] 2

Cycles

Results

This is a less reliable metric that may be of interest but was not used to determine the overall result at the top of this comment.

mean1 range count2
Regressions ❌
(primary)
2.4% [1.7%, 3.0%] 2
Regressions ❌
(secondary)
- - 0
Improvements ✅
(primary)
-2.9% [-2.9%, -2.9%] 1
Improvements ✅
(secondary)
- - 0
All ❌✅ (primary) 0.6% [-2.9%, 3.0%] 3

Footnotes

  1. the arithmetic mean of the percent change 2

  2. number of relevant changes 2

@jackh726 jackh726 deleted the gats-stabilization branch September 13, 2022 14:41
@crlf0710 crlf0710 added the relnotes Marks issues that should be documented in the release notes of the next release. label Sep 13, 2022
taiki-e added a commit to taiki-e/easy-ext that referenced this pull request Sep 14, 2022
generic_associated_types stabilized in Rust 1.65.
rust-lang/rust#96709
@rfcbot rfcbot added finished-final-comment-period The final comment period is finished for this PR / Issue. to-announce Announce this issue on triage meeting and removed final-comment-period In the final comment period and will be merged soon unless new substantive objections are raised. labels Sep 19, 2022
@apiraino apiraino removed the to-announce Announce this issue on triage meeting label Sep 22, 2022
yvt added a commit to r3-os/r3 that referenced this pull request Nov 12, 2022
The `generic_associated_types` feature was stabilized by
[rust-lang/rust#96709][1].

[1]: rust-lang/rust#96709
wip-sync pushed a commit to NetBSD/pkgsrc-wip that referenced this pull request Nov 27, 2022
Pkgsrc changes:
 * We now manage to build for mipsel-unknown-netbsd, but despite the
   target spec saying cpu = "mips3", the compiler manages to emit 64-bit
   instructions which cause "illegal instruction" error.  Will need more
   work.  The mipsel-unknown-netbsd entry is commentd out since there
   is no 1.64.0 bootstrap.
 * Managed to retain the build of aarch64_be, llvm needed a patch to
   avoid use of neon instructions in the BE case (llvm doesn't support
   use of neon in BE mode).  Ref. patch to
   src/llvm-project/llvm/lib/Support/BLAKE3/blake3_impl.h.
 * The minimum gcc version is now 7.x, and that includes the cross-compiler
   for the targets.  For i386 this also needs to /usr/include/gcc-7 include
   files in the target root, because immintrin.h from gcc 5 is not
   compatible with gcc 7.x.  This applies for the targets where we build
   against a root from netbsd-8 (sparc64, powerpc, i386), and files/gcc-wrap
   gets a hack for this.
 * Pick up tweak for -latomic inclusion from
   rust-lang/rust#104220
   and
   rust-lang/rust#104572
 * Retain ability to do 32-bit NetBSD, by changing from 64 to 32 bit
   types in library/std/src/sys/unix/thread_parker/netbsd.rs.
 * I've struggled a bit to get the "openssl-src" build with -latomic
   where it's needed.  I introduce "NetBSD-generic32" system type and
   use it for the NetBSD mipsel target.  There is another attempt to
   do the same in the patch to vendor/openssl-sys/build/main.rs.
 * Bump bootstraps to 1.64.0, checksum updates.

Upstream changes:

Version 1.65.0 (2022-11-03)
==========================

Language
--------
- [Error on `as` casts of enums with `#[non_exhaustive]` variants]
  (rust-lang/rust#92744)
- [Stabilize `let else`](rust-lang/rust#93628)
- [Stabilize generic associated types (GATs)]
  (rust-lang/rust#96709)
- [Add lints `let_underscore_drop`, `let_underscore_lock`, and
  `let_underscore_must_use` from Clippy]
  (rust-lang/rust#97739)
- [Stabilize `break`ing from arbitrary labeled blocks ("label-break-value")]
  (rust-lang/rust#99332)
- [Uninitialized integers, floats, and raw pointers are now considered
  immediate UB](rust-lang/rust#98919).
  Usage of `MaybeUninit` is the correct way to work with uninitialized
  memory.
- [Stabilize raw-dylib for Windows x86_64, aarch64, and thumbv7a]
  (rust-lang/rust#99916)
- [Do not allow `Drop` impl on foreign ADTs]
  (rust-lang/rust#99576)

Compiler
--------
- [Stabilize -Csplit-debuginfo on Linux]
  (rust-lang/rust#98051)
- [Use niche-filling optimization even when multiple variants have
  data] (rust-lang/rust#94075)
- [Associated type projections are now verified to be well-formed
  prior to resolving the underlying type]
  (rust-lang/rust#99217)
- [Stringify non-shorthand visibility correctly]
  (rust-lang/rust#100350)
- [Normalize struct field types when unsizing]
  (rust-lang/rust#101831)
- [Update to LLVM 15](rust-lang/rust#99464)
- [Fix aarch64 call abi to correctly zeroext when needed]
  (rust-lang/rust#97800)
- [debuginfo: Generalize C++-like encoding for enums]
  (rust-lang/rust#98393)
- [Add `special_module_name` lint]
  (rust-lang/rust#94467)
- [Add support for generating unique profraw files by default when
  using `-C instrument-coverage`]
  (rust-lang/rust#100384)
- [Allow dynamic linking for iOS/tvOS targets]
  (rust-lang/rust#100636)

New targets:
- [Add armv4t-none-eabi as a tier 3 target]
  (rust-lang/rust#100244)
- [Add powerpc64-unknown-openbsd and riscv64-unknown-openbsd as tier 3 targets]
  (rust-lang/rust#101025)
- Refer to Rust's [platform support page][platform-support-doc] for more
  information on Rust's tiered platform support.

Libraries
---------
- [Don't generate `PartialEq::ne` in derive(PartialEq)]
  (rust-lang/rust#98655)
- [Windows RNG: Use `BCRYPT_RNG_ALG_HANDLE` by default]
  (rust-lang/rust#101325)
- [Forbid mixing `System` with direct system allocator calls]
  (rust-lang/rust#101394)
- [Document no support for writing to non-blocking stdio/stderr]
  (rust-lang/rust#101416)
- [`std::layout::Layout` size must not overflow `isize::MAX` when
  rounded up to `align`](rust-lang/rust#95295)
  This also changes the safety conditions on
  `Layout::from_size_align_unchecked`.

Stabilized APIs
---------------
- [`std::backtrace::Backtrace`]
  (https://doc.rust-lang.org/stable/std/backtrace/struct.Backtrace.html)
- [`Bound::as_ref`]
  (https://doc.rust-lang.org/stable/std/ops/enum.Bound.html#method.as_ref)
- [`std::io::read_to_string`]
  (https://doc.rust-lang.org/stable/std/io/fn.read_to_string.html)
- [`<*const T>::cast_mut`]
  (https://doc.rust-lang.org/stable/std/primitive.pointer.html#method.cast_mut)
- [`<*mut T>::cast_const`]
  (https://doc.rust-lang.org/stable/std/primitive.pointer.html#method.cast_const)

These APIs are now stable in const contexts:
- [`<*const T>::offset_from`]
  (https://doc.rust-lang.org/stable/std/primitive.pointer.html#method.offset_from)
- [`<*mut T>::offset_from`]
  (https://doc.rust-lang.org/stable/std/primitive.pointer.html#method.offset_from)

Cargo
-----
- [Apply GitHub fast path even for partial hashes]
  (rust-lang/cargo#10807)
- [Do not add home bin path to PATH if it's already there]
  (rust-lang/cargo#11023)
- [Take priority into account within the pending queue]
  (rust-lang/cargo#11032).
  This slightly optimizes job scheduling by Cargo, with typically
  small improvements on larger crate graph builds.

Compatibility Notes
-------------------
- [`std::layout::Layout` size must not overflow `isize::MAX` when
  rounded up to `align`] (rust-lang/rust#95295).
  This also changes the safety conditions on
  `Layout::from_size_align_unchecked`.
- [`PollFn` now only implements `Unpin` if the closure is `Unpin`]
  (rust-lang/rust#102737).
  This is a possible breaking change if users were relying on the
  blanket unpin implementation.  See discussion on the PR for
  details of why this change was made.
- [Drop ExactSizeIterator impl from std::char::EscapeAscii]
  (rust-lang/rust#99880)
  This is a backwards-incompatible change to the standard library's
  surface area, but is unlikely to affect real world usage.
- [Do not consider a single repeated lifetime eligible for elision
  in the return type] (rust-lang/rust#103450)
  This behavior was unintentionally changed in 1.64.0, and this
  release reverts that change by making this an error again.
- [Reenable disabled early syntax gates as future-incompatibility
  lints] (rust-lang/rust#99935)
- [Update the minimum external LLVM to 13]
  (rust-lang/rust#100460)
- [Don't duplicate file descriptors into stdio fds]
  (rust-lang/rust#101426)
- [Sunset RLS](rust-lang/rust#100863)
- [Deny usage of `#![cfg_attr(..., crate_type = ...)]` to set the
  crate type] (rust-lang/rust#99784)
  This strengthens the forward compatibility lint
  deprecated_cfg_attr_crate_type_name to deny.
- [`llvm-has-rust-patches` allows setting the build system to treat
  the LLVM as having Rust-specific patches]
  (rust-lang/rust#101072)
  This option may need to be set for distributions that are building
  Rust with a patched LLVM via `llvm-config`, not the built-in
  LLVM.

Internal Changes
----------------

These changes do not affect any public interfaces of Rust, but they represent
significant improvements to the performance or internals of rustc and related
tools.

- [Add `x.sh` and `x.ps1` shell scripts]
  (rust-lang/rust#99992)
- [compiletest: use target cfg instead of hard-coded tables]
  (rust-lang/rust#100260)
- [Use object instead of LLVM for reading bitcode from rlibs]
  (rust-lang/rust#98100)
- [Enable MIR inlining for optimized compilations]
  (rust-lang/rust#91743)
  This provides a 3-10% improvement in compiletimes for real world
  crates. See [perf results]
  (https://perf.rust-lang.org/compare.html?start=aedf78e56b2279cc869962feac5153b6ba7001ed&end=0075bb4fad68e64b6d1be06bf2db366c30bc75e1&stat=instructions:u).
netbsd-srcmastr pushed a commit to NetBSD/pkgsrc that referenced this pull request Jan 23, 2023
Pkgsrc changes:
 * pkglint cleanups, bump bootstrap kits to 1.65.0.
 * New target: mipsel-unknown-netbsd, for cpu=mips32 with soft-float.
 * Managed to retain the build of aarch64_be, llvm needed a patch to
   avoid use of neon instructions in the BE case (llvm doesn't support
   use of neon in BE mode).  Ref. patch to
   src/llvm-project/llvm/lib/Support/BLAKE3/blake3_impl.h.
   Also submitted upstream of LLVM to the BLAKE3 maintainers.
 * The minimum gcc version is now 7.x, and that includes the
   cross-compiler for the targets.  For i386 this also needs to
   /usr/include/gcc-7 include files in the target root, because
   immintrin.h from gcc 5 is not compatible with gcc 7.x.  This
   applies for the targets where we build against a root from netbsd-8
   (sparc64, powerpc, i386), and files/gcc-wrap gets a hack for this.
 * Pick up tweak for -latomic inclusion from
   rust-lang/rust#104220
   and
   rust-lang/rust#104572
 * Retain ability to do 32-bit NetBSD, by changing from 64 to 32 bit
   types in library/std/src/sys/unix/thread_parker/netbsd.rs.
 * I've tried to get the "openssl-src" build with -latomic where it's
   needed.  I've introduced the "NetBSD-generic32" system type and use
   it for the NetBSD mipsel target.  There is another attempt to do
   the same in the patch to vendor/openssl-sys/build/main.rs.


Upstream changes:

Version 1.66.1 (2023-01-10)
===========================

- Added validation of SSH host keys for git URLs in Cargo
  ([CVE-2022-46176](https://www.cve.org/CVERecord?id=CVE-2022-46176))


Version 1.66.0 (2022-12-15)
===========================

Language
--------
- [Permit specifying explicit discriminants on all `repr(Int)`
  enums](rust-lang/rust#95710)
  ```rust
  #[repr(u8)]
  enum Foo {
      A(u8) = 0,
      B(i8) = 1,
      C(bool) = 42,
  }
  ```
- [Allow transmutes between the same type differing only in
  lifetimes](rust-lang/rust#101520)
- [Change constant evaluation errors from a deny-by-default lint to a
  hard error](rust-lang/rust#102091)
- [Trigger `must_use` on `impl Trait` for
  supertraits](rust-lang/rust#102287) This
  makes `impl ExactSizeIterator` respect the existing `#[must_use]`
  annotation on `Iterator`.
- [Allow `..X` and `..=X` in
  patterns](rust-lang/rust#102275)
- [Uplift `clippy::for_loops_over_fallibles` lint into
  rustc](rust-lang/rust#99696)
- [Stabilize `sym` operands in inline
  assembly](rust-lang/rust#103168)
- [Update to Unicode 15](rust-lang/rust#101912)
- [Opaque types no longer imply lifetime
  bounds](rust-lang/rust#95474) This is a
  soundness fix which may break code that was erroneously relying on this
  behavior.

Compiler
--------
- [Add armv5te-none-eabi and thumbv5te-none-eabi tier 3
  targets](rust-lang/rust#101329)
  - Refer to Rust's [platform support page][platform-support-doc] for
    more information on Rust's tiered platform support.
- [Add support for linking against macOS universal
  libraries](rust-lang/rust#98736)

Libraries
---------
- [Fix `#[derive(Default)]` on a generic `#[default]` enum adding
  unnecessary `Default`
  bounds](rust-lang/rust#101040)
- [Update to Unicode 15](rust-lang/rust#101821)

Stabilized APIs
---------------
- [`proc_macro::Span::source_text`](https://doc.rust-lang.org/stable/proc_macro/struct.Span.html#method.source_text)
- [`uX::{checked_add_signed, overflowing_add_signed,
  saturating_add_signed,
  wrapping_add_signed}`](https://doc.rust-lang.org/stable/std/primitive.u8.html#method.checked_add_signed)
- [`iX::{checked_add_unsigned, overflowing_add_unsigned,
  saturating_add_unsigned,
  wrapping_add_unsigned}`](https://doc.rust-lang.org/stable/std/primitive.i8.html#method.checked_add_unsigned)
- [`iX::{checked_sub_unsigned, overflowing_sub_unsigned,
  saturating_sub_unsigned,
  wrapping_sub_unsigned}`](https://doc.rust-lang.org/stable/std/primitive.i8.html#method.checked_sub_unsigned)
- [`BTreeSet::{first, last, pop_first,
  pop_last}`](https://doc.rust-lang.org/stable/std/collections/struct.BTreeSet.html#method.first)
- [`BTreeMap::{first_key_value, last_key_value, first_entry, last_entry,
  pop_first,
  pop_last}`](https://doc.rust-lang.org/stable/std/collections/struct.BTreeMap.html#method.first_key_value)
- [Add `AsFd` implementations for stdio lock types on
  WASI.](rust-lang/rust#101768)
- [`impl TryFrom<Vec<T>> for Box<[T;
  N]>`](https://doc.rust-lang.org/stable/std/boxed/struct.Box.html#impl-TryFrom%3CVec%3CT%2C%20Global%3E%3E-for-Box%3C%5BT%3B%20N%5D%2C%20Global%3E)
- [`core::hint::black_box`](https://doc.rust-lang.org/stable/std/hint/fn.black_box.html)
- [`Duration::try_from_secs_{f32,f64}`](https://doc.rust-lang.org/stable/std/time/struct.Duration.html#method.try_from_secs_f32)
- [`Option::unzip`](https://doc.rust-lang.org/stable/std/option/enum.Option.html#method.unzip)
- [`std::os::fd`](https://doc.rust-lang.org/stable/std/os/fd/index.html)

Rustdoc
-------
- [Add Rustdoc warning for invalid HTML tags in the
  documentation](rust-lang/rust#101720)

Cargo
-----
- [Added `cargo remove` to remove dependencies from
  Cargo.toml](https://doc.rust-lang.org/nightly/cargo/commands/cargo-remove.html)
- [`cargo publish` now waits for the new version to be downloadable
  before exiting](rust-lang/cargo#11062)

See [detailed release notes](https://github.com/rust-lang/cargo/blob/master/CHANGELOG.md#cargo-166-2022-12-15) for more.

Compatibility Notes
-------------------
- [Only apply `ProceduralMasquerade` hack to older versions of
  `rental`](rust-lang/rust#94063)
- [Don't export `__heap_base` and `__data_end` on
  wasm32-wasi.](rust-lang/rust#102385)
- [Don't export `__wasm_init_memory` on
  WebAssembly.](rust-lang/rust#102426)
- [Only export `__tls_*` on
  wasm32-unknown-unknown.](rust-lang/rust#102440)
- [Don't link to `libresolv` in libstd on
  Darwin](rust-lang/rust#102766)
- [Update libstd's libc to 0.2.135 (to make `libstd` no longer pull in
  `libiconv.dylib` on
  Darwin)](rust-lang/rust#103277)
- [Opaque types no longer imply lifetime
  bounds](rust-lang/rust#95474)
  This is a soundness fix which may break code that was erroneously
  relying on this behavior.
- [Make `order_dependent_trait_objects` show up in future-breakage
  reports](rust-lang/rust#102635)
- [Change std::process::Command spawning to default to inheriting the
  parent's signal mask](rust-lang/rust#101077)

Internal Changes
----------------

These changes do not affect any public interfaces of Rust, but they
represent significant improvements to the performance or internals of
rustc and related tools.

- [Enable BOLT for LLVM
  compilation](rust-lang/rust#94381)
- [Enable LTO for
  rustc_driver.so](rust-lang/rust#101403)


Version 1.65.0 (2022-11-03)
==========================

Language
--------
- [Error on `as` casts of enums with `#[non_exhaustive]` variants]
  (rust-lang/rust#92744)
- [Stabilize `let else`](rust-lang/rust#93628)
- [Stabilize generic associated types (GATs)]
  (rust-lang/rust#96709)
- [Add lints `let_underscore_drop`, `let_underscore_lock`, and
  `let_underscore_must_use` from Clippy]
  (rust-lang/rust#97739)
- [Stabilize `break`ing from arbitrary labeled blocks ("label-break-value")]
  (rust-lang/rust#99332)
- [Uninitialized integers, floats, and raw pointers are now considered
  immediate UB](rust-lang/rust#98919).
  Usage of `MaybeUninit` is the correct way to work with uninitialized
  memory.
- [Stabilize raw-dylib for Windows x86_64, aarch64, and thumbv7a]
  (rust-lang/rust#99916)
- [Do not allow `Drop` impl on foreign ADTs]
  (rust-lang/rust#99576)

Compiler
--------
- [Stabilize -Csplit-debuginfo on Linux]
  (rust-lang/rust#98051)
- [Use niche-filling optimization even when multiple variants have
  data] (rust-lang/rust#94075)
- [Associated type projections are now verified to be well-formed
  prior to resolving the underlying type]
  (rust-lang/rust#99217)
- [Stringify non-shorthand visibility correctly]
  (rust-lang/rust#100350)
- [Normalize struct field types when unsizing]
  (rust-lang/rust#101831)
- [Update to LLVM 15](rust-lang/rust#99464)
- [Fix aarch64 call abi to correctly zeroext when needed]
  (rust-lang/rust#97800)
- [debuginfo: Generalize C++-like encoding for enums]
  (rust-lang/rust#98393)
- [Add `special_module_name` lint]
  (rust-lang/rust#94467)
- [Add support for generating unique profraw files by default when
  using `-C instrument-coverage`]
  (rust-lang/rust#100384)
- [Allow dynamic linking for iOS/tvOS targets]
  (rust-lang/rust#100636)

New targets:
- [Add armv4t-none-eabi as a tier 3 target]
  (rust-lang/rust#100244)
- [Add powerpc64-unknown-openbsd and riscv64-unknown-openbsd as tier 3 targets]
  (rust-lang/rust#101025)
- Refer to Rust's [platform support page][platform-support-doc] for more
  information on Rust's tiered platform support.

Libraries
---------
- [Don't generate `PartialEq::ne` in derive(PartialEq)]
  (rust-lang/rust#98655)
- [Windows RNG: Use `BCRYPT_RNG_ALG_HANDLE` by default]
  (rust-lang/rust#101325)
- [Forbid mixing `System` with direct system allocator calls]
  (rust-lang/rust#101394)
- [Document no support for writing to non-blocking stdio/stderr]
  (rust-lang/rust#101416)
- [`std::layout::Layout` size must not overflow `isize::MAX` when
  rounded up to `align`](rust-lang/rust#95295)
  This also changes the safety conditions on
  `Layout::from_size_align_unchecked`.

Stabilized APIs
---------------
- [`std::backtrace::Backtrace`]
  (https://doc.rust-lang.org/stable/std/backtrace/struct.Backtrace.html)
- [`Bound::as_ref`]
  (https://doc.rust-lang.org/stable/std/ops/enum.Bound.html#method.as_ref)
- [`std::io::read_to_string`]
  (https://doc.rust-lang.org/stable/std/io/fn.read_to_string.html)
- [`<*const T>::cast_mut`]
  (https://doc.rust-lang.org/stable/std/primitive.pointer.html#method.cast_mut)
- [`<*mut T>::cast_const`]
  (https://doc.rust-lang.org/stable/std/primitive.pointer.html#method.cast_const)

These APIs are now stable in const contexts:
- [`<*const T>::offset_from`]
  (https://doc.rust-lang.org/stable/std/primitive.pointer.html#method.offset_from)
- [`<*mut T>::offset_from`]
  (https://doc.rust-lang.org/stable/std/primitive.pointer.html#method.offset_from)

Cargo
-----
- [Apply GitHub fast path even for partial hashes]
  (rust-lang/cargo#10807)
- [Do not add home bin path to PATH if it's already there]
  (rust-lang/cargo#11023)
- [Take priority into account within the pending queue]
  (rust-lang/cargo#11032).
  This slightly optimizes job scheduling by Cargo, with typically
  small improvements on larger crate graph builds.

Compatibility Notes
-------------------
- [`std::layout::Layout` size must not overflow `isize::MAX` when
  rounded up to `align`] (rust-lang/rust#95295).
  This also changes the safety conditions on
  `Layout::from_size_align_unchecked`.
- [`PollFn` now only implements `Unpin` if the closure is `Unpin`]
  (rust-lang/rust#102737).
  This is a possible breaking change if users were relying on the
  blanket unpin implementation.  See discussion on the PR for
  details of why this change was made.
- [Drop ExactSizeIterator impl from std::char::EscapeAscii]
  (rust-lang/rust#99880)
  This is a backwards-incompatible change to the standard library's
  surface area, but is unlikely to affect real world usage.
- [Do not consider a single repeated lifetime eligible for elision
  in the return type] (rust-lang/rust#103450)
  This behavior was unintentionally changed in 1.64.0, and this
  release reverts that change by making this an error again.
- [Reenable disabled early syntax gates as future-incompatibility
  lints] (rust-lang/rust#99935)
- [Update the minimum external LLVM to 13]
  (rust-lang/rust#100460)
- [Don't duplicate file descriptors into stdio fds]
  (rust-lang/rust#101426)
- [Sunset RLS](rust-lang/rust#100863)
- [Deny usage of `#![cfg_attr(..., crate_type = ...)]` to set the
  crate type] (rust-lang/rust#99784)
  This strengthens the forward compatibility lint
  deprecated_cfg_attr_crate_type_name to deny.
- [`llvm-has-rust-patches` allows setting the build system to treat
  the LLVM as having Rust-specific patches]
  (rust-lang/rust#101072)
  This option may need to be set for distributions that are building
  Rust with a patched LLVM via `llvm-config`, not the built-in
  LLVM.

Internal Changes
----------------

These changes do not affect any public interfaces of Rust, but they represent
significant improvements to the performance or internals of rustc and related
tools.

- [Add `x.sh` and `x.ps1` shell scripts]
  (rust-lang/rust#99992)
- [compiletest: use target cfg instead of hard-coded tables]
  (rust-lang/rust#100260)
- [Use object instead of LLVM for reading bitcode from rlibs]
  (rust-lang/rust#98100)
- [Enable MIR inlining for optimized compilations]
  (rust-lang/rust#91743)
  This provides a 3-10% improvement in compiletimes for real world
  crates. See [perf results]
  (https://perf.rust-lang.org/compare.html?start=aedf78e56b2279cc869962feac5153b6ba7001ed&end=0075bb4fad68e64b6d1be06bf2db366c30bc75e1&stat=instructions:u).
calebcartwright pushed a commit to calebcartwright/rustfmt that referenced this pull request Jan 24, 2023
Stabilize generic associated types

Closes #44265

r? `@nikomatsakis`

# ⚡ Status of the discussion ⚡

* [x] There have been several serious concerns raised, [summarized here](rust-lang/rust#96709 (comment)).
* [x] There has also been a [deep-dive comment](rust-lang/rust#96709 (comment)) explaining some of the "patterns of code" that are enabled by GATs, based on use-cases posted to this thread or on the tracking issue.
* [x] We have modeled some aspects of GATs in [a-mir-formality](https://github.com/nikomatsakis/a-mir-formality) to give better confidence in how they will be resolved in the future. [You can read a write-up here](https://github.com/rust-lang/types-team/blob/master/minutes/2022-07-08-implied-bounds-and-wf-checking.md).
* [x] The major points of the discussion have been [summarized on the GAT initiative repository](https://rust-lang.github.io/generic-associated-types-initiative/mvp.html).
* [x] [FCP has been proposed](rust-lang/rust#96709 (comment)) and we are awaiting final decisions and discussion amidst the relevant team members.

# Stabilization proposal

This PR proposes the stabilization of `#![feature(generic_associated_types)]`. While there a number of future additions to be made and bugs to be fixed (both discussed below), properly doing these will require significant language design and will ultimately likely be backwards-compatible. Given the overwhelming desire to have some form of generic associated types (GATs) available on stable and the stability of the "simple" uses, stabilizing the current subset of GAT features is almost certainly the correct next step.

Tracking issue: #44265
Initiative: https://rust-lang.github.io/generic-associated-types-initiative/
RFC: https://github.com/rust-lang/rfcs/blob/master/text/1598-generic_associated_types.md
Version: 1.65 (2022-08-22 => beta, 2022-11-03 => stable).

## Motivation

There are a myriad of potential use cases for GATs. Stabilization unblocks probable future language features (e.g. async functions in traits), potential future standard library features (e.g. a `LendingIterator` or some form of `Iterator` with a lifetime generic), and a plethora of user use cases (some of which can be seen just by scrolling through the tracking issue and looking at all the issues linking to it).

There are a myriad of potential use cases for GATs. First, there are many users that have chosen to not use GATs primarily because they are not stable (some of which can be seen just by scrolling through the tracking issue and looking at all the issues linking to it). Second, while language feature desugaring isn't *blocked* on stabilization, it gives more confidence on using the feature. Likewise, library features like `LendingIterator` are not necessarily blocked on stabilization to be implemented unstably; however few, if any, public-facing APIs actually use unstable features.

This feature has a long history of design, discussion, and developement - the RFC was first introduced roughly 6 years ago. While there are still a number of features left to implement and bugs left to fix, it's clear that it's unlikely those will have backwards-incompatibility concerns. Additionally, the bugs that do exist do not strongly impede the most-common use cases.

## What is stabilized

The primary language feature stabilized here is the ability to have generics on associated types, as so. Additionally, where clauses on associated types will now be accepted, regardless if the associated type is generic or not.

```rust
trait ATraitWithGATs {
    type Assoc<'a, T> where T: 'a;
}

trait ATraitWithoutGATs<'a, T> {
    type Assoc where T: 'a;
}
```

When adding an impl for a trait with generic associated types, the generics for the associated type are copied as well. Note that where clauses are allowed both after the specified type and before the equals sign; however, the latter is a warn-by-default deprecation.

```rust
struct X;
struct Y;

impl ATraitWithGATs for X {
    type Assoc<'a, T> = &'a T
      where T: 'a;
}
impl ATraitWithGATs for Y {
    type Assoc<'a, T>
      where T: 'a
    = &'a T;
}
```

To use a GAT in a function, generics are specified on the associated type, as if it was a struct or enum. GATs can also be specified in trait bounds:

```rust
fn accepts_gat<'a, T>(t: &'a T) -> T::Assoc<'a, T>
  where for<'x> T: ATraitWithGATs<Assoc<'a, T> = &'a T> {
    ...
}
```

GATs can also appear in trait methods. However, depending on how they are used, they may confer where clauses on the associated type definition. More information can be found [here](rust-lang/rust#87479). Briefly, where clauses are required when those bounds can be proven in the methods that *construct* the GAT or other associated types that use the GAT in the trait. This allows impls to have maximum flexibility in the types defined for the associated type.

To take a relatively simple example:

```rust
trait Iterable {
    type Item<'a>;
    type Iterator<'a>: Iterator<Item = Self::Item<'a>>;

    fn iter<'x>(&'x self) -> Self::Iterator<'x>;
    //^ We know that `Self: 'a` for `Iterator<'a>`, so we require that bound on `Iterator`
    //  `Iterator` uses `Self::Item`, so we also require a `Self: 'a` on `Item` too
}
```

A couple well-explained examples are available in a previous [blog post](https://blog.rust-lang.org/2021/08/03/GATs-stabilization-push.html).

## What isn't stabilized/implemented

### Universal type/const quantification

Currently, you can write a bound like `X: for<'a> Trait<Assoc<'a> = &'a ()>`. However, you cannot currently write `for<T> X: Trait<Assoc<T> = T>` or `for<const N> X: Trait<Assoc<N> = [usize; N]>`.

Here is an example where this is needed:

```rust
trait Foo {}

trait Trait {
    type Assoc<F: Foo>;
}

trait Trait2: Sized {
    fn foo<F: Foo, T: Trait<Assoc<F> = F>>(_t: T);
}
```

In the above example, the *caller* must specify `F`, which is likely not what is desired.

### Object-safe GATs

Unlike non-generic associated types, traits with GATs are not currently object-safe. In other words the following are not allowed:

```rust
trait Trait {
    type Assoc<'a>;
}

fn foo(t: &dyn for<'a> Trait<Assoc<'a> = &'a ()>) {}
         //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not allowed

let ty: Box<dyn for<'a> Trait<Assoc<'a> = &'a ()>>;
          //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not allowed
```

### Higher-kinded types

You cannot write currently (and there are no current plans to implement this):

```rust
struct Struct<'a> {}

fn foo(s: for<'a> Struct<'a>) {}
```

## Tests

There are many tests covering GATs that can be found in  `src/test/ui/generic-associated-types`. Here, I'll list (in alphanumeric order) tests highlight some important behavior or contain important patterns.

- `./parse/*`: Parsing of GATs in traits and impls, and the trait path with GATs
- `./collections-project-default.rs`: Interaction with associated type defaults
- `./collections.rs`: The `Collection` pattern
- `./const-generics-gat-in-trait-return-type-*.rs`: Const parameters
- `./constraint-assoc-type-suggestion.rs`: Emit correct syntax in suggestion
- `./cross-crate-bounds.rs`: Ensure we handles bounds across crates the same
- `./elided-in-expr-position.rs`: Disallow lifetime elision in return position
- `./gat-in-trait-path-undeclared-lifetime.rs`: Ensure we error on undeclared lifetime in trait path
- `./gat-in-trait-path.rs`: Base trait path case
- `./gat-trait-path-generic-type-arg.rs`: Don't allow shadowing of parameters
- `./gat-trait-path-parenthesised-args.rs`: Don't allow paranthesized args in trait path
- `./generic-associated-types-where.rs`: Ensure that we require where clauses from trait to be met on impl
- `./impl_bounds.rs`: Check that the bounds on GATs in an impl are checked
- `./issue-76826.rs`: `Windows` pattern
- `./issue-78113-lifetime-mismatch-dyn-trait-box.rs`: Implicit 'static diagnostics
- `./issue-84931.rs`: Ensure that we have a where clause on GAT to ensure trait parameter lives long enough
- `./issue-87258_a.rs`: Unconstrained opaque type with TAITs
- `./issue-87429-2.rs`: Ensure we can use bound vars in the bounds
- `./issue-87429-associated-type-default.rs`: Ensure bounds hold with associated type defaults, for both trait and impl
- `./issue-87429-specialization.rs`: Check that bounds hold under specialization
- `./issue-88595.rs`: Under the outlives lint, we require a bound for both trait and GAT lifetime when trait lifetime is used in function
- `./issue-90014.rs`: Lifetime bounds are checked with TAITs
- `./issue-91139.rs`: Under migrate mode, but not NLL, we don't capture implied bounds from HRTB lifetimes used in a function and GATs
- `./issue-91762.rs`: We used to too eagerly pick param env candidates when normalizing with GATs. We now require explicit parameters specified.
- `./issue-95305.rs`: Disallow lifetime elision in trait paths
- `./iterable.rs`: `Iterable` pattern
- `./method-unsatified-assoc-type-predicate.rs`: Print predicates with GATs correctly in method resolve error
- `./missing_lifetime_const.rs`: Ensure we must specify lifetime args (not elidable)
- `./missing-where-clause-on-trait.rs`: Ensure we don't allow stricter bounds on impl than trait
- `./parameter_number_and_kind_impl.rs`: Ensure paramters on GAT in impl match GAT in trait
- `./pointer_family.rs`: `PointerFamily` pattern
- `./projection-bound-cycle.rs`: Don't allow invalid cycles to prove bounds
- `./self-outlives-lint.rs`: Ensures that an e.g. `Self: 'a` is written on the traits GAT if that bound can be implied from the GAT usage in the trait
- `./shadowing.rs`: Don't allow lifetime shadowing in params
- `./streaming_iterator.rs`: `StreamingIterator`(`LendingIterator`) pattern
- `./trait-objects.rs`: Disallow trait objects for traits with GATs
- `./variance_constraints.rs`: Require that GAT substs be invariant

## Remaining bugs and open issues

A full list of remaining open issues can be found at: https://github.com/rust-lang/rust/labels/F-generic_associated_types

There are some `known-bug` tests in-tree at `src/test/ui/generic-associated-types/bugs`.

Here I'll categorize most of those that GAT bugs (or involve a pattern found more with GATs), but not those that include GATs but not a GAT issue in and of itself. (I also won't include issues directly for things listed elsewhere here.)

Using the concrete type of a GAT instead of the projection type can give errors, since lifetimes are chosen to be early-bound vs late-bound.
- #85533
- #87803

In certain cases, we can run into cycle or overflow errors. This is more generally a problem with associated types.
- #87755
- #87758

Bounds on an associatd type need to be proven by an impl, but where clauses need to be proven by the usage. This can lead to confusion when users write one when they mean the other.
- #87831
- #90573

We sometimes can't normalize closure signatures fully. Really an asociated types issue, but might happen a bit more frequently with GATs, since more obvious place for HRTB lifetimes.
- #88382

When calling a function, we assign types to parameters "too late", after we already try (and fail) to normalize projections. Another associated types issue that might pop up more with GATs.
- #88460
- #96230

We don't fully have implied bounds for lifetimes appearing in GAT trait paths, which can lead to unconstrained type errors.
- #88526

Suggestion for adding lifetime bounds can suggest unhelpful fixes (`T: 'a` instead of `Self: 'a`), but the next compiler error after making the suggested change is helpful.
- #90816
- #92096
- #95268

We can end up requiring that `for<'a> I: 'a` when we really want `for<'a where I: 'a> I: 'a`. This can leave unhelpful errors than effectively can't be satisfied unless `I: 'static`. Requires bigger changes and not only GATs.
- #91693

Unlike with non-generic associated types, we don't eagerly normalize with param env candidates. This is intended behavior (for now), to avoid accidentaly stabilizing picking arbitrary impls.
- #91762

Some Iterator adapter patterns (namely `filter`) require Polonius or unsafe to work.
- #92985

## Potential Future work

### Universal type/const quantification

No work has been done to implement this. There are also some questions around implied bounds.

###  Object-safe GATs

The intention is to make traits with GATs object-safe. There are some design work to be done around well-formedness rules and general implementation.

### GATified std lib types

It would be helpful to either introduce new std lib traits (like `LendingIterator`) or to modify existing ones (adding a `'a` generic to `Iterator::Item`). There also a number of other candidates, like `Index`/`IndexMut` and `Fn`/`FnMut`/`FnOnce`.

### Reduce the need for `for<'a>`

Seen [here](rust-lang/rfcs#1598 (comment)). One possible syntax:

```rust
trait Iterable {
    type Iter<'a>: Iterator<Item = Self::Item<'a>>;
}

fn foo<T>() where T: Iterable, T::Item<let 'a>: Display { } //note the `let`!
```

### Better implied bounds on higher-ranked things

Currently if we have a `type Item<'a> where self: 'a`, and a `for<'a> T: Iterator<Item<'a> = &'a ()`, this requires `for<'a> Self: 'a`. Really, we want `for<'a where T: 'a> ...`

There was some mentions of this all the back in the RFC thread [here](rust-lang/rfcs#1598 (comment)).

## Alternatives

### Make generics on associated type in bounds a binder

Imagine the bound `for<'a> T: Trait<Item<'a>= &'a ()>`. It might be that `for<'a>` is "too large" and it should instead be `T: Trait<for<'a> Item<'a>= &'a ()>`. Brought up in RFC thread [here](rust-lang/rfcs#1598 (comment)) and in a few places since.

Another related question: Is `for<'a>` the right syntax? Maybe `where<'a>`? Also originally found in RFC thread [here](rust-lang/rfcs#1598 (comment)).

### Stabilize lifetime GATs first

This has been brought up a few times. The idea is to only allow GATs with lifetime parameters to in initial stabilization. This was probably most useful prior to actual implementation. At this point, lifetimes, types, and consts are all implemented and work. It feels like an arbitrary split without strong reason.

## History

* On 2016-04-30, [RFC opened](rust-lang/rfcs#1598)
* On 2017-09-02, RFC merged and [tracking issue opened](rust-lang/rust#44265)
* On 2017-10-23, [Move Generics from MethodSig to TraitItem and ImplItem](rust-lang/rust#44766)
* On 2017-12-01, [Generic Associated Types Parsing & Name Resolution](rust-lang/rust#45904)
* On 2017-12-15, [rust-lang/rust#46706
* On 2018-04-23, [Feature gate where clauses on associated types](rust-lang/rust#49368)
* On 2018-05-10, [Extend tests for RFC1598 (GAT)](rust-lang/rust#49423)
* On 2018-05-24, [Finish implementing GATs (Chalk)](rust-lang/chalk#134)
* On 2019-12-21, [Make GATs less ICE-prone](rust-lang/rust#67160)
* On 2020-02-13, [fix lifetime shadowing check in GATs](rust-lang/rust#68938)
* On 2020-06-20, [Projection bound validation](rust-lang/rust#72788)
* On 2020-10-06, [Separate projection bounds and predicates](rust-lang/rust#73905)
* On 2021-02-05, [Generic associated types in trait paths](rust-lang/rust#79554)
* On 2021-02-06, [Trait objects do not work with generic associated types](rust-lang/rust#81823)
* On 2021-04-28, [Make traits with GATs not object safe](rust-lang/rust#84622)
* On 2021-05-11, [Improve diagnostics for GATs](rust-lang/rust#82272)
* On 2021-07-16, [Make GATs no longer an incomplete feature](rust-lang/rust#84623)
* On 2021-07-16, [Replace associated item bound vars with placeholders when projecting](rust-lang/rust#86993)
* On 2021-07-26, [GATs: Decide whether to have defaults for `where Self: 'a`](rust-lang/rust#87479)
* On 2021-08-25, [Normalize projections under binders](rust-lang/rust#85499)
* On 2021-08-03, [The push for GATs stabilization](https://blog.rust-lang.org/2021/08/03/GATs-stabilization-push.html)
* On 2021-08-12, [Detect stricter constraints on gats where clauses in impls vs trait](rust-lang/rust#88336)
* On 2021-09-20, [Proposal: Change syntax of where clauses on type aliases](rust-lang/rust#89122)
* On 2021-11-06, [Implementation of GATs outlives lint](rust-lang/rust#89970)
* On 2021-12-29. [Parse and suggest moving where clauses after equals for type aliases](rust-lang/rust#92118)
* On 2022-01-15, [Ignore static lifetimes for GATs outlives lint](rust-lang/rust#92865)
* On 2022-02-08, [Don't constrain projection predicates with inference vars in GAT substs](rust-lang/rust#92917)
* On 2022-02-15, [Rework GAT where clause check](rust-lang/rust#93820)
* On 2022-02-19, [Only mark projection as ambiguous if GAT substs are constrained](rust-lang/rust#93892)
* On 2022-03-03, [Support GATs in Rustdoc](rust-lang/rust#94009)
* On 2022-03-06, [Change location of where clause on GATs](rust-lang/rust#90076)
* On 2022-05-04, [A shiny future with GATs blog post](https://jackh726.github.io/rust/2022/05/04/a-shiny-future-with-gats.html)
* On 2022-05-04, [Stabilization PR](rust-lang/rust#96709)
RalfJung pushed a commit to RalfJung/rust-analyzer that referenced this pull request Apr 20, 2024
Stabilize generic associated types

Closes #44265

r? `@nikomatsakis`

# ⚡ Status of the discussion ⚡

* [x] There have been several serious concerns raised, [summarized here](rust-lang/rust#96709 (comment)).
* [x] There has also been a [deep-dive comment](rust-lang/rust#96709 (comment)) explaining some of the "patterns of code" that are enabled by GATs, based on use-cases posted to this thread or on the tracking issue.
* [x] We have modeled some aspects of GATs in [a-mir-formality](https://github.com/nikomatsakis/a-mir-formality) to give better confidence in how they will be resolved in the future. [You can read a write-up here](https://github.com/rust-lang/types-team/blob/master/minutes/2022-07-08-implied-bounds-and-wf-checking.md).
* [x] The major points of the discussion have been [summarized on the GAT initiative repository](https://rust-lang.github.io/generic-associated-types-initiative/mvp.html).
* [x] [FCP has been proposed](rust-lang/rust#96709 (comment)) and we are awaiting final decisions and discussion amidst the relevant team members.

# Stabilization proposal

This PR proposes the stabilization of `#![feature(generic_associated_types)]`. While there a number of future additions to be made and bugs to be fixed (both discussed below), properly doing these will require significant language design and will ultimately likely be backwards-compatible. Given the overwhelming desire to have some form of generic associated types (GATs) available on stable and the stability of the "simple" uses, stabilizing the current subset of GAT features is almost certainly the correct next step.

Tracking issue: #44265
Initiative: https://rust-lang.github.io/generic-associated-types-initiative/
RFC: https://github.com/rust-lang/rfcs/blob/master/text/1598-generic_associated_types.md
Version: 1.65 (2022-08-22 => beta, 2022-11-03 => stable).

## Motivation

There are a myriad of potential use cases for GATs. Stabilization unblocks probable future language features (e.g. async functions in traits), potential future standard library features (e.g. a `LendingIterator` or some form of `Iterator` with a lifetime generic), and a plethora of user use cases (some of which can be seen just by scrolling through the tracking issue and looking at all the issues linking to it).

There are a myriad of potential use cases for GATs. First, there are many users that have chosen to not use GATs primarily because they are not stable (some of which can be seen just by scrolling through the tracking issue and looking at all the issues linking to it). Second, while language feature desugaring isn't *blocked* on stabilization, it gives more confidence on using the feature. Likewise, library features like `LendingIterator` are not necessarily blocked on stabilization to be implemented unstably; however few, if any, public-facing APIs actually use unstable features.

This feature has a long history of design, discussion, and developement - the RFC was first introduced roughly 6 years ago. While there are still a number of features left to implement and bugs left to fix, it's clear that it's unlikely those will have backwards-incompatibility concerns. Additionally, the bugs that do exist do not strongly impede the most-common use cases.

## What is stabilized

The primary language feature stabilized here is the ability to have generics on associated types, as so. Additionally, where clauses on associated types will now be accepted, regardless if the associated type is generic or not.

```rust
trait ATraitWithGATs {
    type Assoc<'a, T> where T: 'a;
}

trait ATraitWithoutGATs<'a, T> {
    type Assoc where T: 'a;
}
```

When adding an impl for a trait with generic associated types, the generics for the associated type are copied as well. Note that where clauses are allowed both after the specified type and before the equals sign; however, the latter is a warn-by-default deprecation.

```rust
struct X;
struct Y;

impl ATraitWithGATs for X {
    type Assoc<'a, T> = &'a T
      where T: 'a;
}
impl ATraitWithGATs for Y {
    type Assoc<'a, T>
      where T: 'a
    = &'a T;
}
```

To use a GAT in a function, generics are specified on the associated type, as if it was a struct or enum. GATs can also be specified in trait bounds:

```rust
fn accepts_gat<'a, T>(t: &'a T) -> T::Assoc<'a, T>
  where for<'x> T: ATraitWithGATs<Assoc<'a, T> = &'a T> {
    ...
}
```

GATs can also appear in trait methods. However, depending on how they are used, they may confer where clauses on the associated type definition. More information can be found [here](rust-lang/rust#87479). Briefly, where clauses are required when those bounds can be proven in the methods that *construct* the GAT or other associated types that use the GAT in the trait. This allows impls to have maximum flexibility in the types defined for the associated type.

To take a relatively simple example:

```rust
trait Iterable {
    type Item<'a>;
    type Iterator<'a>: Iterator<Item = Self::Item<'a>>;

    fn iter<'x>(&'x self) -> Self::Iterator<'x>;
    //^ We know that `Self: 'a` for `Iterator<'a>`, so we require that bound on `Iterator`
    //  `Iterator` uses `Self::Item`, so we also require a `Self: 'a` on `Item` too
}
```

A couple well-explained examples are available in a previous [blog post](https://blog.rust-lang.org/2021/08/03/GATs-stabilization-push.html).

## What isn't stabilized/implemented

### Universal type/const quantification

Currently, you can write a bound like `X: for<'a> Trait<Assoc<'a> = &'a ()>`. However, you cannot currently write `for<T> X: Trait<Assoc<T> = T>` or `for<const N> X: Trait<Assoc<N> = [usize; N]>`.

Here is an example where this is needed:

```rust
trait Foo {}

trait Trait {
    type Assoc<F: Foo>;
}

trait Trait2: Sized {
    fn foo<F: Foo, T: Trait<Assoc<F> = F>>(_t: T);
}
```

In the above example, the *caller* must specify `F`, which is likely not what is desired.

### Object-safe GATs

Unlike non-generic associated types, traits with GATs are not currently object-safe. In other words the following are not allowed:

```rust
trait Trait {
    type Assoc<'a>;
}

fn foo(t: &dyn for<'a> Trait<Assoc<'a> = &'a ()>) {}
         //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not allowed

let ty: Box<dyn for<'a> Trait<Assoc<'a> = &'a ()>>;
          //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not allowed
```

### Higher-kinded types

You cannot write currently (and there are no current plans to implement this):

```rust
struct Struct<'a> {}

fn foo(s: for<'a> Struct<'a>) {}
```

## Tests

There are many tests covering GATs that can be found in  `src/test/ui/generic-associated-types`. Here, I'll list (in alphanumeric order) tests highlight some important behavior or contain important patterns.

- `./parse/*`: Parsing of GATs in traits and impls, and the trait path with GATs
- `./collections-project-default.rs`: Interaction with associated type defaults
- `./collections.rs`: The `Collection` pattern
- `./const-generics-gat-in-trait-return-type-*.rs`: Const parameters
- `./constraint-assoc-type-suggestion.rs`: Emit correct syntax in suggestion
- `./cross-crate-bounds.rs`: Ensure we handles bounds across crates the same
- `./elided-in-expr-position.rs`: Disallow lifetime elision in return position
- `./gat-in-trait-path-undeclared-lifetime.rs`: Ensure we error on undeclared lifetime in trait path
- `./gat-in-trait-path.rs`: Base trait path case
- `./gat-trait-path-generic-type-arg.rs`: Don't allow shadowing of parameters
- `./gat-trait-path-parenthesised-args.rs`: Don't allow paranthesized args in trait path
- `./generic-associated-types-where.rs`: Ensure that we require where clauses from trait to be met on impl
- `./impl_bounds.rs`: Check that the bounds on GATs in an impl are checked
- `./issue-76826.rs`: `Windows` pattern
- `./issue-78113-lifetime-mismatch-dyn-trait-box.rs`: Implicit 'static diagnostics
- `./issue-84931.rs`: Ensure that we have a where clause on GAT to ensure trait parameter lives long enough
- `./issue-87258_a.rs`: Unconstrained opaque type with TAITs
- `./issue-87429-2.rs`: Ensure we can use bound vars in the bounds
- `./issue-87429-associated-type-default.rs`: Ensure bounds hold with associated type defaults, for both trait and impl
- `./issue-87429-specialization.rs`: Check that bounds hold under specialization
- `./issue-88595.rs`: Under the outlives lint, we require a bound for both trait and GAT lifetime when trait lifetime is used in function
- `./issue-90014.rs`: Lifetime bounds are checked with TAITs
- `./issue-91139.rs`: Under migrate mode, but not NLL, we don't capture implied bounds from HRTB lifetimes used in a function and GATs
- `./issue-91762.rs`: We used to too eagerly pick param env candidates when normalizing with GATs. We now require explicit parameters specified.
- `./issue-95305.rs`: Disallow lifetime elision in trait paths
- `./iterable.rs`: `Iterable` pattern
- `./method-unsatified-assoc-type-predicate.rs`: Print predicates with GATs correctly in method resolve error
- `./missing_lifetime_const.rs`: Ensure we must specify lifetime args (not elidable)
- `./missing-where-clause-on-trait.rs`: Ensure we don't allow stricter bounds on impl than trait
- `./parameter_number_and_kind_impl.rs`: Ensure paramters on GAT in impl match GAT in trait
- `./pointer_family.rs`: `PointerFamily` pattern
- `./projection-bound-cycle.rs`: Don't allow invalid cycles to prove bounds
- `./self-outlives-lint.rs`: Ensures that an e.g. `Self: 'a` is written on the traits GAT if that bound can be implied from the GAT usage in the trait
- `./shadowing.rs`: Don't allow lifetime shadowing in params
- `./streaming_iterator.rs`: `StreamingIterator`(`LendingIterator`) pattern
- `./trait-objects.rs`: Disallow trait objects for traits with GATs
- `./variance_constraints.rs`: Require that GAT substs be invariant

## Remaining bugs and open issues

A full list of remaining open issues can be found at: https://github.com/rust-lang/rust/labels/F-generic_associated_types

There are some `known-bug` tests in-tree at `src/test/ui/generic-associated-types/bugs`.

Here I'll categorize most of those that GAT bugs (or involve a pattern found more with GATs), but not those that include GATs but not a GAT issue in and of itself. (I also won't include issues directly for things listed elsewhere here.)

Using the concrete type of a GAT instead of the projection type can give errors, since lifetimes are chosen to be early-bound vs late-bound.
- #85533
- #87803

In certain cases, we can run into cycle or overflow errors. This is more generally a problem with associated types.
- #87755
- #87758

Bounds on an associatd type need to be proven by an impl, but where clauses need to be proven by the usage. This can lead to confusion when users write one when they mean the other.
- #87831
- #90573

We sometimes can't normalize closure signatures fully. Really an asociated types issue, but might happen a bit more frequently with GATs, since more obvious place for HRTB lifetimes.
- #88382

When calling a function, we assign types to parameters "too late", after we already try (and fail) to normalize projections. Another associated types issue that might pop up more with GATs.
- #88460
- #96230

We don't fully have implied bounds for lifetimes appearing in GAT trait paths, which can lead to unconstrained type errors.
- #88526

Suggestion for adding lifetime bounds can suggest unhelpful fixes (`T: 'a` instead of `Self: 'a`), but the next compiler error after making the suggested change is helpful.
- #90816
- #92096
- #95268

We can end up requiring that `for<'a> I: 'a` when we really want `for<'a where I: 'a> I: 'a`. This can leave unhelpful errors than effectively can't be satisfied unless `I: 'static`. Requires bigger changes and not only GATs.
- #91693

Unlike with non-generic associated types, we don't eagerly normalize with param env candidates. This is intended behavior (for now), to avoid accidentaly stabilizing picking arbitrary impls.
- #91762

Some Iterator adapter patterns (namely `filter`) require Polonius or unsafe to work.
- #92985

## Potential Future work

### Universal type/const quantification

No work has been done to implement this. There are also some questions around implied bounds.

###  Object-safe GATs

The intention is to make traits with GATs object-safe. There are some design work to be done around well-formedness rules and general implementation.

### GATified std lib types

It would be helpful to either introduce new std lib traits (like `LendingIterator`) or to modify existing ones (adding a `'a` generic to `Iterator::Item`). There also a number of other candidates, like `Index`/`IndexMut` and `Fn`/`FnMut`/`FnOnce`.

### Reduce the need for `for<'a>`

Seen [here](rust-lang/rfcs#1598 (comment)). One possible syntax:

```rust
trait Iterable {
    type Iter<'a>: Iterator<Item = Self::Item<'a>>;
}

fn foo<T>() where T: Iterable, T::Item<let 'a>: Display { } //note the `let`!
```

### Better implied bounds on higher-ranked things

Currently if we have a `type Item<'a> where self: 'a`, and a `for<'a> T: Iterator<Item<'a> = &'a ()`, this requires `for<'a> Self: 'a`. Really, we want `for<'a where T: 'a> ...`

There was some mentions of this all the back in the RFC thread [here](rust-lang/rfcs#1598 (comment)).

## Alternatives

### Make generics on associated type in bounds a binder

Imagine the bound `for<'a> T: Trait<Item<'a>= &'a ()>`. It might be that `for<'a>` is "too large" and it should instead be `T: Trait<for<'a> Item<'a>= &'a ()>`. Brought up in RFC thread [here](rust-lang/rfcs#1598 (comment)) and in a few places since.

Another related question: Is `for<'a>` the right syntax? Maybe `where<'a>`? Also originally found in RFC thread [here](rust-lang/rfcs#1598 (comment)).

### Stabilize lifetime GATs first

This has been brought up a few times. The idea is to only allow GATs with lifetime parameters to in initial stabilization. This was probably most useful prior to actual implementation. At this point, lifetimes, types, and consts are all implemented and work. It feels like an arbitrary split without strong reason.

## History

* On 2016-04-30, [RFC opened](rust-lang/rfcs#1598)
* On 2017-09-02, RFC merged and [tracking issue opened](rust-lang/rust#44265)
* On 2017-10-23, [Move Generics from MethodSig to TraitItem and ImplItem](rust-lang/rust#44766)
* On 2017-12-01, [Generic Associated Types Parsing & Name Resolution](rust-lang/rust#45904)
* On 2017-12-15, [rust-lang/rust#46706
* On 2018-04-23, [Feature gate where clauses on associated types](rust-lang/rust#49368)
* On 2018-05-10, [Extend tests for RFC1598 (GAT)](rust-lang/rust#49423)
* On 2018-05-24, [Finish implementing GATs (Chalk)](rust-lang/chalk#134)
* On 2019-12-21, [Make GATs less ICE-prone](rust-lang/rust#67160)
* On 2020-02-13, [fix lifetime shadowing check in GATs](rust-lang/rust#68938)
* On 2020-06-20, [Projection bound validation](rust-lang/rust#72788)
* On 2020-10-06, [Separate projection bounds and predicates](rust-lang/rust#73905)
* On 2021-02-05, [Generic associated types in trait paths](rust-lang/rust#79554)
* On 2021-02-06, [Trait objects do not work with generic associated types](rust-lang/rust#81823)
* On 2021-04-28, [Make traits with GATs not object safe](rust-lang/rust#84622)
* On 2021-05-11, [Improve diagnostics for GATs](rust-lang/rust#82272)
* On 2021-07-16, [Make GATs no longer an incomplete feature](rust-lang/rust#84623)
* On 2021-07-16, [Replace associated item bound vars with placeholders when projecting](rust-lang/rust#86993)
* On 2021-07-26, [GATs: Decide whether to have defaults for `where Self: 'a`](rust-lang/rust#87479)
* On 2021-08-25, [Normalize projections under binders](rust-lang/rust#85499)
* On 2021-08-03, [The push for GATs stabilization](https://blog.rust-lang.org/2021/08/03/GATs-stabilization-push.html)
* On 2021-08-12, [Detect stricter constraints on gats where clauses in impls vs trait](rust-lang/rust#88336)
* On 2021-09-20, [Proposal: Change syntax of where clauses on type aliases](rust-lang/rust#89122)
* On 2021-11-06, [Implementation of GATs outlives lint](rust-lang/rust#89970)
* On 2021-12-29. [Parse and suggest moving where clauses after equals for type aliases](rust-lang/rust#92118)
* On 2022-01-15, [Ignore static lifetimes for GATs outlives lint](rust-lang/rust#92865)
* On 2022-02-08, [Don't constrain projection predicates with inference vars in GAT substs](rust-lang/rust#92917)
* On 2022-02-15, [Rework GAT where clause check](rust-lang/rust#93820)
* On 2022-02-19, [Only mark projection as ambiguous if GAT substs are constrained](rust-lang/rust#93892)
* On 2022-03-03, [Support GATs in Rustdoc](rust-lang/rust#94009)
* On 2022-03-06, [Change location of where clause on GATs](rust-lang/rust#90076)
* On 2022-05-04, [A shiny future with GATs blog post](https://jackh726.github.io/rust/2022/05/04/a-shiny-future-with-gats.html)
* On 2022-05-04, [Stabilization PR](rust-lang/rust#96709)
RalfJung pushed a commit to RalfJung/rust-analyzer that referenced this pull request Apr 27, 2024
Stabilize generic associated types

Closes #44265

r? `@nikomatsakis`

# ⚡ Status of the discussion ⚡

* [x] There have been several serious concerns raised, [summarized here](rust-lang/rust#96709 (comment)).
* [x] There has also been a [deep-dive comment](rust-lang/rust#96709 (comment)) explaining some of the "patterns of code" that are enabled by GATs, based on use-cases posted to this thread or on the tracking issue.
* [x] We have modeled some aspects of GATs in [a-mir-formality](https://github.com/nikomatsakis/a-mir-formality) to give better confidence in how they will be resolved in the future. [You can read a write-up here](https://github.com/rust-lang/types-team/blob/master/minutes/2022-07-08-implied-bounds-and-wf-checking.md).
* [x] The major points of the discussion have been [summarized on the GAT initiative repository](https://rust-lang.github.io/generic-associated-types-initiative/mvp.html).
* [x] [FCP has been proposed](rust-lang/rust#96709 (comment)) and we are awaiting final decisions and discussion amidst the relevant team members.

# Stabilization proposal

This PR proposes the stabilization of `#![feature(generic_associated_types)]`. While there a number of future additions to be made and bugs to be fixed (both discussed below), properly doing these will require significant language design and will ultimately likely be backwards-compatible. Given the overwhelming desire to have some form of generic associated types (GATs) available on stable and the stability of the "simple" uses, stabilizing the current subset of GAT features is almost certainly the correct next step.

Tracking issue: #44265
Initiative: https://rust-lang.github.io/generic-associated-types-initiative/
RFC: https://github.com/rust-lang/rfcs/blob/master/text/1598-generic_associated_types.md
Version: 1.65 (2022-08-22 => beta, 2022-11-03 => stable).

## Motivation

There are a myriad of potential use cases for GATs. Stabilization unblocks probable future language features (e.g. async functions in traits), potential future standard library features (e.g. a `LendingIterator` or some form of `Iterator` with a lifetime generic), and a plethora of user use cases (some of which can be seen just by scrolling through the tracking issue and looking at all the issues linking to it).

There are a myriad of potential use cases for GATs. First, there are many users that have chosen to not use GATs primarily because they are not stable (some of which can be seen just by scrolling through the tracking issue and looking at all the issues linking to it). Second, while language feature desugaring isn't *blocked* on stabilization, it gives more confidence on using the feature. Likewise, library features like `LendingIterator` are not necessarily blocked on stabilization to be implemented unstably; however few, if any, public-facing APIs actually use unstable features.

This feature has a long history of design, discussion, and developement - the RFC was first introduced roughly 6 years ago. While there are still a number of features left to implement and bugs left to fix, it's clear that it's unlikely those will have backwards-incompatibility concerns. Additionally, the bugs that do exist do not strongly impede the most-common use cases.

## What is stabilized

The primary language feature stabilized here is the ability to have generics on associated types, as so. Additionally, where clauses on associated types will now be accepted, regardless if the associated type is generic or not.

```rust
trait ATraitWithGATs {
    type Assoc<'a, T> where T: 'a;
}

trait ATraitWithoutGATs<'a, T> {
    type Assoc where T: 'a;
}
```

When adding an impl for a trait with generic associated types, the generics for the associated type are copied as well. Note that where clauses are allowed both after the specified type and before the equals sign; however, the latter is a warn-by-default deprecation.

```rust
struct X;
struct Y;

impl ATraitWithGATs for X {
    type Assoc<'a, T> = &'a T
      where T: 'a;
}
impl ATraitWithGATs for Y {
    type Assoc<'a, T>
      where T: 'a
    = &'a T;
}
```

To use a GAT in a function, generics are specified on the associated type, as if it was a struct or enum. GATs can also be specified in trait bounds:

```rust
fn accepts_gat<'a, T>(t: &'a T) -> T::Assoc<'a, T>
  where for<'x> T: ATraitWithGATs<Assoc<'a, T> = &'a T> {
    ...
}
```

GATs can also appear in trait methods. However, depending on how they are used, they may confer where clauses on the associated type definition. More information can be found [here](rust-lang/rust#87479). Briefly, where clauses are required when those bounds can be proven in the methods that *construct* the GAT or other associated types that use the GAT in the trait. This allows impls to have maximum flexibility in the types defined for the associated type.

To take a relatively simple example:

```rust
trait Iterable {
    type Item<'a>;
    type Iterator<'a>: Iterator<Item = Self::Item<'a>>;

    fn iter<'x>(&'x self) -> Self::Iterator<'x>;
    //^ We know that `Self: 'a` for `Iterator<'a>`, so we require that bound on `Iterator`
    //  `Iterator` uses `Self::Item`, so we also require a `Self: 'a` on `Item` too
}
```

A couple well-explained examples are available in a previous [blog post](https://blog.rust-lang.org/2021/08/03/GATs-stabilization-push.html).

## What isn't stabilized/implemented

### Universal type/const quantification

Currently, you can write a bound like `X: for<'a> Trait<Assoc<'a> = &'a ()>`. However, you cannot currently write `for<T> X: Trait<Assoc<T> = T>` or `for<const N> X: Trait<Assoc<N> = [usize; N]>`.

Here is an example where this is needed:

```rust
trait Foo {}

trait Trait {
    type Assoc<F: Foo>;
}

trait Trait2: Sized {
    fn foo<F: Foo, T: Trait<Assoc<F> = F>>(_t: T);
}
```

In the above example, the *caller* must specify `F`, which is likely not what is desired.

### Object-safe GATs

Unlike non-generic associated types, traits with GATs are not currently object-safe. In other words the following are not allowed:

```rust
trait Trait {
    type Assoc<'a>;
}

fn foo(t: &dyn for<'a> Trait<Assoc<'a> = &'a ()>) {}
         //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not allowed

let ty: Box<dyn for<'a> Trait<Assoc<'a> = &'a ()>>;
          //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not allowed
```

### Higher-kinded types

You cannot write currently (and there are no current plans to implement this):

```rust
struct Struct<'a> {}

fn foo(s: for<'a> Struct<'a>) {}
```

## Tests

There are many tests covering GATs that can be found in  `src/test/ui/generic-associated-types`. Here, I'll list (in alphanumeric order) tests highlight some important behavior or contain important patterns.

- `./parse/*`: Parsing of GATs in traits and impls, and the trait path with GATs
- `./collections-project-default.rs`: Interaction with associated type defaults
- `./collections.rs`: The `Collection` pattern
- `./const-generics-gat-in-trait-return-type-*.rs`: Const parameters
- `./constraint-assoc-type-suggestion.rs`: Emit correct syntax in suggestion
- `./cross-crate-bounds.rs`: Ensure we handles bounds across crates the same
- `./elided-in-expr-position.rs`: Disallow lifetime elision in return position
- `./gat-in-trait-path-undeclared-lifetime.rs`: Ensure we error on undeclared lifetime in trait path
- `./gat-in-trait-path.rs`: Base trait path case
- `./gat-trait-path-generic-type-arg.rs`: Don't allow shadowing of parameters
- `./gat-trait-path-parenthesised-args.rs`: Don't allow paranthesized args in trait path
- `./generic-associated-types-where.rs`: Ensure that we require where clauses from trait to be met on impl
- `./impl_bounds.rs`: Check that the bounds on GATs in an impl are checked
- `./issue-76826.rs`: `Windows` pattern
- `./issue-78113-lifetime-mismatch-dyn-trait-box.rs`: Implicit 'static diagnostics
- `./issue-84931.rs`: Ensure that we have a where clause on GAT to ensure trait parameter lives long enough
- `./issue-87258_a.rs`: Unconstrained opaque type with TAITs
- `./issue-87429-2.rs`: Ensure we can use bound vars in the bounds
- `./issue-87429-associated-type-default.rs`: Ensure bounds hold with associated type defaults, for both trait and impl
- `./issue-87429-specialization.rs`: Check that bounds hold under specialization
- `./issue-88595.rs`: Under the outlives lint, we require a bound for both trait and GAT lifetime when trait lifetime is used in function
- `./issue-90014.rs`: Lifetime bounds are checked with TAITs
- `./issue-91139.rs`: Under migrate mode, but not NLL, we don't capture implied bounds from HRTB lifetimes used in a function and GATs
- `./issue-91762.rs`: We used to too eagerly pick param env candidates when normalizing with GATs. We now require explicit parameters specified.
- `./issue-95305.rs`: Disallow lifetime elision in trait paths
- `./iterable.rs`: `Iterable` pattern
- `./method-unsatified-assoc-type-predicate.rs`: Print predicates with GATs correctly in method resolve error
- `./missing_lifetime_const.rs`: Ensure we must specify lifetime args (not elidable)
- `./missing-where-clause-on-trait.rs`: Ensure we don't allow stricter bounds on impl than trait
- `./parameter_number_and_kind_impl.rs`: Ensure paramters on GAT in impl match GAT in trait
- `./pointer_family.rs`: `PointerFamily` pattern
- `./projection-bound-cycle.rs`: Don't allow invalid cycles to prove bounds
- `./self-outlives-lint.rs`: Ensures that an e.g. `Self: 'a` is written on the traits GAT if that bound can be implied from the GAT usage in the trait
- `./shadowing.rs`: Don't allow lifetime shadowing in params
- `./streaming_iterator.rs`: `StreamingIterator`(`LendingIterator`) pattern
- `./trait-objects.rs`: Disallow trait objects for traits with GATs
- `./variance_constraints.rs`: Require that GAT substs be invariant

## Remaining bugs and open issues

A full list of remaining open issues can be found at: https://github.com/rust-lang/rust/labels/F-generic_associated_types

There are some `known-bug` tests in-tree at `src/test/ui/generic-associated-types/bugs`.

Here I'll categorize most of those that GAT bugs (or involve a pattern found more with GATs), but not those that include GATs but not a GAT issue in and of itself. (I also won't include issues directly for things listed elsewhere here.)

Using the concrete type of a GAT instead of the projection type can give errors, since lifetimes are chosen to be early-bound vs late-bound.
- #85533
- #87803

In certain cases, we can run into cycle or overflow errors. This is more generally a problem with associated types.
- #87755
- #87758

Bounds on an associatd type need to be proven by an impl, but where clauses need to be proven by the usage. This can lead to confusion when users write one when they mean the other.
- #87831
- #90573

We sometimes can't normalize closure signatures fully. Really an asociated types issue, but might happen a bit more frequently with GATs, since more obvious place for HRTB lifetimes.
- #88382

When calling a function, we assign types to parameters "too late", after we already try (and fail) to normalize projections. Another associated types issue that might pop up more with GATs.
- #88460
- #96230

We don't fully have implied bounds for lifetimes appearing in GAT trait paths, which can lead to unconstrained type errors.
- #88526

Suggestion for adding lifetime bounds can suggest unhelpful fixes (`T: 'a` instead of `Self: 'a`), but the next compiler error after making the suggested change is helpful.
- #90816
- #92096
- #95268

We can end up requiring that `for<'a> I: 'a` when we really want `for<'a where I: 'a> I: 'a`. This can leave unhelpful errors than effectively can't be satisfied unless `I: 'static`. Requires bigger changes and not only GATs.
- #91693

Unlike with non-generic associated types, we don't eagerly normalize with param env candidates. This is intended behavior (for now), to avoid accidentaly stabilizing picking arbitrary impls.
- #91762

Some Iterator adapter patterns (namely `filter`) require Polonius or unsafe to work.
- #92985

## Potential Future work

### Universal type/const quantification

No work has been done to implement this. There are also some questions around implied bounds.

###  Object-safe GATs

The intention is to make traits with GATs object-safe. There are some design work to be done around well-formedness rules and general implementation.

### GATified std lib types

It would be helpful to either introduce new std lib traits (like `LendingIterator`) or to modify existing ones (adding a `'a` generic to `Iterator::Item`). There also a number of other candidates, like `Index`/`IndexMut` and `Fn`/`FnMut`/`FnOnce`.

### Reduce the need for `for<'a>`

Seen [here](rust-lang/rfcs#1598 (comment)). One possible syntax:

```rust
trait Iterable {
    type Iter<'a>: Iterator<Item = Self::Item<'a>>;
}

fn foo<T>() where T: Iterable, T::Item<let 'a>: Display { } //note the `let`!
```

### Better implied bounds on higher-ranked things

Currently if we have a `type Item<'a> where self: 'a`, and a `for<'a> T: Iterator<Item<'a> = &'a ()`, this requires `for<'a> Self: 'a`. Really, we want `for<'a where T: 'a> ...`

There was some mentions of this all the back in the RFC thread [here](rust-lang/rfcs#1598 (comment)).

## Alternatives

### Make generics on associated type in bounds a binder

Imagine the bound `for<'a> T: Trait<Item<'a>= &'a ()>`. It might be that `for<'a>` is "too large" and it should instead be `T: Trait<for<'a> Item<'a>= &'a ()>`. Brought up in RFC thread [here](rust-lang/rfcs#1598 (comment)) and in a few places since.

Another related question: Is `for<'a>` the right syntax? Maybe `where<'a>`? Also originally found in RFC thread [here](rust-lang/rfcs#1598 (comment)).

### Stabilize lifetime GATs first

This has been brought up a few times. The idea is to only allow GATs with lifetime parameters to in initial stabilization. This was probably most useful prior to actual implementation. At this point, lifetimes, types, and consts are all implemented and work. It feels like an arbitrary split without strong reason.

## History

* On 2016-04-30, [RFC opened](rust-lang/rfcs#1598)
* On 2017-09-02, RFC merged and [tracking issue opened](rust-lang/rust#44265)
* On 2017-10-23, [Move Generics from MethodSig to TraitItem and ImplItem](rust-lang/rust#44766)
* On 2017-12-01, [Generic Associated Types Parsing & Name Resolution](rust-lang/rust#45904)
* On 2017-12-15, [rust-lang/rust#46706
* On 2018-04-23, [Feature gate where clauses on associated types](rust-lang/rust#49368)
* On 2018-05-10, [Extend tests for RFC1598 (GAT)](rust-lang/rust#49423)
* On 2018-05-24, [Finish implementing GATs (Chalk)](rust-lang/chalk#134)
* On 2019-12-21, [Make GATs less ICE-prone](rust-lang/rust#67160)
* On 2020-02-13, [fix lifetime shadowing check in GATs](rust-lang/rust#68938)
* On 2020-06-20, [Projection bound validation](rust-lang/rust#72788)
* On 2020-10-06, [Separate projection bounds and predicates](rust-lang/rust#73905)
* On 2021-02-05, [Generic associated types in trait paths](rust-lang/rust#79554)
* On 2021-02-06, [Trait objects do not work with generic associated types](rust-lang/rust#81823)
* On 2021-04-28, [Make traits with GATs not object safe](rust-lang/rust#84622)
* On 2021-05-11, [Improve diagnostics for GATs](rust-lang/rust#82272)
* On 2021-07-16, [Make GATs no longer an incomplete feature](rust-lang/rust#84623)
* On 2021-07-16, [Replace associated item bound vars with placeholders when projecting](rust-lang/rust#86993)
* On 2021-07-26, [GATs: Decide whether to have defaults for `where Self: 'a`](rust-lang/rust#87479)
* On 2021-08-25, [Normalize projections under binders](rust-lang/rust#85499)
* On 2021-08-03, [The push for GATs stabilization](https://blog.rust-lang.org/2021/08/03/GATs-stabilization-push.html)
* On 2021-08-12, [Detect stricter constraints on gats where clauses in impls vs trait](rust-lang/rust#88336)
* On 2021-09-20, [Proposal: Change syntax of where clauses on type aliases](rust-lang/rust#89122)
* On 2021-11-06, [Implementation of GATs outlives lint](rust-lang/rust#89970)
* On 2021-12-29. [Parse and suggest moving where clauses after equals for type aliases](rust-lang/rust#92118)
* On 2022-01-15, [Ignore static lifetimes for GATs outlives lint](rust-lang/rust#92865)
* On 2022-02-08, [Don't constrain projection predicates with inference vars in GAT substs](rust-lang/rust#92917)
* On 2022-02-15, [Rework GAT where clause check](rust-lang/rust#93820)
* On 2022-02-19, [Only mark projection as ambiguous if GAT substs are constrained](rust-lang/rust#93892)
* On 2022-03-03, [Support GATs in Rustdoc](rust-lang/rust#94009)
* On 2022-03-06, [Change location of where clause on GATs](rust-lang/rust#90076)
* On 2022-05-04, [A shiny future with GATs blog post](https://jackh726.github.io/rust/2022/05/04/a-shiny-future-with-gats.html)
* On 2022-05-04, [Stabilization PR](rust-lang/rust#96709)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-rustdoc-json Area: Rustdoc JSON backend disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. F-generic_associated_types `#![feature(generic_associated_types)]` a.k.a. GATs finished-final-comment-period The final comment period is finished for this PR / Issue. merged-by-bors This PR was explicitly merged by bors. relnotes Marks issues that should be documented in the release notes of the next release. S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. S-waiting-on-fcp Status: PR is in FCP and is awaiting for FCP to complete. T-lang Relevant to the language team, which will review and decide on the PR/issue. T-types Relevant to the types team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

🔬 Tracking issue for generic associated types (GAT)