-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
Conversation
Some changes occurred in src/tools/rustfmt. cc @rust-lang/rustfmt |
Thank you @jackh726. Like, really, thank you very much! |
d688e42
to
7e6e507
Compare
☔ The latest upstream changes (presumably #96593) made this pull request unmergeable. Please resolve the merge conflicts. |
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. ComplexityRust 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. ExpressivityGATs 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 |
@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.
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 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. |
@zesterer Can you please explain on how you use GAT to speedup your parser? |
Parser combinators are parsers composed of smaller parsers, similar to how There are a number of cases where it's necessary to parse a pattern without actually evaluating its output. For example, This can be very wasteful though, particularly if the creation of 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 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. |
wild alternative number 3: only stabilize type GATswhile 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 |
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 With all that said, I do unfortunately tend to agree with quite a bit of what @nrc is saying.
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 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. |
In response to @nrc (and making every section collapsible to not take up 2 vertical screen widths): "GATs nearly always increase complexity"
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:
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
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"
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"
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"
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"
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 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 BurntSushiAlso 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?
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:
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. |
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 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
Yeah, I struggle a lot with that in |
@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? |
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.
I have absolutely no idea. I have no tools for how to even think about an answer to this question. :-/
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:
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 This is a really important point. Because if I try to write that 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:
|
@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 |
@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 Secondly, the |
@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 |
it's far from fair to try and imply a single aspect of Haskell (the degree of abstraction) is the sole cause...
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! |
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. |
@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. |
I'll address some technical points later, but for now...
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. |
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 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! |
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>;
} |
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 ( |
Finished benchmarking commit (7098c18): comparison URL. Overall result: no relevant changes - no action needed@rustbot label: -perf-regression Instruction countThis benchmark run did not return any relevant results for this metric. Max RSS (memory usage)ResultsThis 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.
CyclesResultsThis 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.
Footnotes |
generic_associated_types stabilized in Rust 1.65. rust-lang/rust#96709
The `generic_associated_types` feature was stabilized by [rust-lang/rust#96709][1]. [1]: rust-lang/rust#96709
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).
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).
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, [https://github.com/rust-lang/rust/pull/46706](https://github.com/rust-lang/rust/pull/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)
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, [https://github.com/rust-lang/rust/pull/46706](https://github.com/rust-lang/rust/pull/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)
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, [https://github.com/rust-lang/rust/pull/46706](https://github.com/rust-lang/rust/pull/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)
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 ofIterator
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.
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.
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:
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:
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 writefor<T> X: Trait<Assoc<T> = T>
orfor<const N> X: Trait<Assoc<N> = [usize; N]>
.Here is an example where this is needed:
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:
Higher-kinded types
You cannot write currently (and there are no current plans to implement this):
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
: TheCollection
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 invariantRemaining 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 atsrc/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.
for<'a> F: FnOnce(Self::Gat<'a>)
#87758Bounds 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 ofSelf: 'a
), but the next compiler error after making the suggested change is helpful.where Self: 'a
GAT bound suggested #90816Self: 'a
bounds break someasync
blocks withimpl Trait
#92096We can end up requiring that
for<'a> I: 'a
when we really wantfor<'a where I: 'a> I: 'a
. This can leave unhelpful errors than effectively can't be satisfied unlessI: 'static
. Requires bigger changes and not only GATs.for_each
method #91693Unlike 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.LendingIterator
requires Polonius #92985Potential 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 toIterator::Item
). There also a number of other candidates, likeIndex
/IndexMut
andFn
/FnMut
/FnOnce
.Reduce the need for
for<'a>
Seen here. One possible syntax:
Better implied bounds on higher-ranked things
Currently if we have a
type Item<'a> where self: 'a
, and afor<'a> T: Iterator<Item<'a> = &'a ()
, this requiresfor<'a> Self: 'a
. Really, we wantfor<'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 thatfor<'a>
is "too large" and it should instead beT: 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? Maybewhere<'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
where Self: 'a