Rust stabilizes generic associated types
github.com
github.com
Writing combinators that work generically over Fn/FnMut/FnOnce (for a very simple case, try writing the "compose" function - currently you have to write all three implementations separately even though they're identical).
Writing code that works polymorphically between sync and async - this solves the "function colouring" problem without having to give up precise control of where yields happen.
Writing data structure traversals (e.g. a tree walker/visitor) that can handle some kind of extra "context" in a generic way (e.g. updating a state, or making a database call, or indeed making an async call).
https://blog.rust-lang.org/inside-rust/2022/07/27/keyword-ge...
What happened with these? I'm out of the loop.
(This 2020 blogpost https://www.fpcomplete.com/blog/monads-gats-nightly-rust/ does a nice job of describing one such pattern, though their choice of Unwrapped and Wrapped<> as identifiers is overly obscure.)
https://github.com/rust-lang/rust/pull/96709#issuecomment-11...
I don't think this makes the language more confusing, because putting an integer inside of the generics brackets is something that previously you might try to do, if you didn't know it wasn't allowed. In fact, I would go so far as to say that this is a compiler feature, rather than a language feature, because it fills in a semantic hole that already existed in the way Rust programmers thought about Rust types. :-)
min_const_generics have been on stable for while
The idea is that any types which Rust can see for itself can be trivially compared for equality will eventually qualify for this purpose, so your Hat enumeration, or a structure with four integers and a boolean in it, or anything that Rust says "Yeah, I'll just memcmp that for equality" would qualify. No floating point numbers, no strings, nothing actually crazy. But this is in some undefined future, for now we just have min_const_generics which is basically the fundamental integral types as constants in generics.
It seems like a pattern for languages that try to be more pragmatic. Instead of a Big Idea, turtles-upon-turtles approach that would take years to finalize in a form that doesn’t invalidate everything that came before it, one goes for an initially good enough approach and then release more and more features which to the insider might seem very holistic but to an outsider (like me) just sound like Static Runtime-Bounded Polyhydral Lambda Cube Type Capabilities and Associates.
So from my point of view, I didn't really need to understand what GATs were bringing - it's a lifting of restrictions that would have previously felt arbitrary.
By analogy, imagine a version of C where you can't use a struct as a member of a union. Then, a new version of C comes along letting you use structs in unions. We could call that a new feature, "struct-in-unions"... or we could just call it lifting a restriction on an existing feature. It's entirely natural to want to use structs with unions, and it's probably what a learner of the language intuitively expects should be possible. Likewise, a learner of Rust probably expects that any type can be used as an associated type, and before today that wasn't true, and after today that is true.
Yes, precisely. This is the literally what parent was talking about, "programming language abstractions [...] stacking on top of each other"
The parent was echoing the common complaint (exclusively from people who don't actually use rust, in my experience) that rust is getting "too many features."
But as others in this thread has pointed out, features like GATs actually make the language simpler, because they're removing weird (and to a new user, unexpected) restrictions on existing features. This will then unblock removing other surprising restrictions, like the inability to use async functions in traits.
Rust takes the philosophy that it's better to do hard work in the compiler in order to make the language simpler and easier for users. That's what's happening here.
Rust gets a lot of unfair criticism of "adding new stuff" when a lot of what is "added" is actually removing restrictions and making it where a developer has less to learn.
I have been learning Rust by writing some toy code over the weekends, and 9 out of 10 times when I hit a roadblock and went to my local Rust Users Group, the answer was "just wait for GAT". Turns out these patterns occur organically out of fairly mundane needs of abstraction (e.g. the now-infamous `LendingIterator`), and especially for me as a C++17/20 daily driver, _arbitrary_ limitations on expressivity.
Don't get me wrong --- Rust feels a lot more expressive than C++17/20 in the vast majority of areas, but for the rest maybe 10%, it's pothole land for stable Rust. Eliminating some of these potholes is a big win for Rust's adoption story.
the most common example is the lending iterator. Have a struct that owns a vector of data. Can you implement an iterator that can return a reference of each item when you call next()? Not without GATs, because each time you return from next() you're creating a new lifetime that doesn't exist in your trait definition.
That's why current standard library iterators are basically structs that own a reference to what you iterate. See the GAT RFC which explains this concept much better than I can:
https://github.com/rust-lang/rfcs/blob/master/text/1598-gene...
TL;DR it's not a "new" feature per se but something that could have been present from the beginning without making the language different.
GATs (generic associated types) allow you to define type, lifetime, or const generics on associated types. If you're familiar with languages that have "higher-kinded types", then you could call GATs type constructors on traits.
So more flexible APIs and more efficient code for the consumer, and simpler library code (which is usually an improvement in reliability and maintainability).
https://github.com/rust-lang/rust/pull/96709#issuecomment-11... Has an extensive list of libraries wanting to use GATs, with their justifications.
trait LendingIterator<'a, T> {
fn next(&'a mut self) -> Option<&'a T>;
}
impl<'a, T> LendingIterator<'a, T> for Whatever {
fn next(&'a mut self) -> Option<&'a T> {
todo!()
}
}
And of course, you'd modify the for loop syntax to desugar appropriately if the the iterator implements LendingIterator.There are other possible uses, but those two are ones a typical Rust developer may have already run into.
> Allow type constructors to be associated with traits. This is an incremental step toward a more general feature commonly called "higher-kinded types," which is often ranked highly as a requested feature by Rust users.
The RFC is probably the best documentation for now.