Design patterns

A natural question with GATs is to ask "what are they used for?" Because GATs represent a kind of "fundamental capability" of traits, though, that question can be difficult to answer in a short summary -- they can be used for all kinds of things! Therefore, this section attempts to answer by summarizing "design patterns" that we have seen in the wild that are enabled by GATs. These patterns are described through a "deep dive" into a particular example, often of a crate in the wild; but they represent patterns that could be extracted and applied in other cases.

List of projects using GATs

Over the years, many people have posted examples of how they would like to use GATs. compiler-errors compiled a mostly complete list which was posted to the stabilization issue. We reproduce that list here:

Projects using GATs

  • connector-x - uses GATs to provide zero-copy interfaces to load data from DBs.
  • kas - uses Generic Associated Types to avoid the unsafety around draw_handle and size_handle, fixing a possible memory-safety violation in the process.
    • "generic associated types remove a usage of unsafe (revealing a bug in the process), and are almost certainly the way forward (once the compiler properly supports this)"
  • raft-engine - uses GATs in a generic builder pattern

Blocked (at least in part) by GATs:

  • Rust in the linux kernel - https://github.com/Rust-for-Linux/linux/issues/2
  • udoprog/audio - "My goal is to author a set of traits and data structures that can be used across audio libraries in the ecosystem. I'm currently holding off on GATs landing, since that's needed to provide proper abstractions without incurring a runtime overhead."
  • graphene - "This would allow the result types of most Graph methods to be impl Iterator, such that implementors can use whatever implementation they want. To achieve this currently we are using Box<Iterator> as return types which is inefficient."
  • ion-rust - "Currently, delegation via our current APIs are difficult because we cannot have an associated type that can be constructed with a per invocation lifetime. This example illustrates this, and shows how we can work around this by the judicious use of unsafe code[...]"
  • proptest - GATs could be used to represent non-owned values
  • ink - GATs could be used to simplify macro codegen around trait impl
  • objc2 - Could use GATs to abstract over a generic Reference type, simplifying two methods into one
  • mpris-rs - GATs could be used to abstract over an iterator type
  • dioxus - "It allows some property of a node to depend on both the state of it's children and it's parent. Specifically this would make text wrapping, and overflow possible"
  • sophia_rs - An other way to go would be to have an "rdf-api" crate similar to what RDF/JS is doing for the RDF models and its commons extensions. And have Oxigraph and Sophia and hopefully the other RDF related libraries in Rust use it. But it might be hard to build a nice and efficient API without GAT.

Other miscellaneous mentions of GATs, or GATs blocked a rewrite but workarounds were found

  • veracruz - The workaround here is to require the associated-type implementations to all be "lifetime-less", which probably requires unsafe code in the implementations.
  • embedded-nal - Using a typestate pattern to represent UDP trait's states
  • linfa - For now though, there are several limitations to the implementation due to a lack of type-system features. For instance, it has to return a Vec of references to points because the lifetime to &self has to show up somewhere in the return type, and we don't have access to GATs yet. Basically, I get around these issues by performing more allocation than we should [...]
  • heed - Initial rewrite of a trait relied on GATs, was eventually worked around but has its own limitations?
  • ockam - https://github.com/build-trust/ockam/issues/1564
  • rust-imap - could benefit with a GATified Index trait
  • anchors - "GAT will let us skip cloning during map"
  • capnproto-rust - The main obstacle is that getting this to work (particularly with capnp::traits::Owned) probably requires generic associated types, and it's unclear when those will land on stable rust.
  • gamma - Could use GATs to return iterators instead of having to box them, possibly providing a more general API without the slowdown of boxing
  • dicom-rs - GAT could be used to representing lifetime of borrowed data
  • rust-multihash - Apparently could use GATs to get around const-generics limitations
  • libprio-rs - Doing better will require a feature called "generic associated types" (per the SO above). Unfortunately, GATs are not stabilized yet; it looks like they are set to be stabilized in a few months.
  • gluesql - Could use GATs to turn a trait into an associated type(i think)
  • pushgen - mentioned that things could be simplified by GATs (or RPITIT)
  • plexus - "Until GATs land in stable Rust, this change requires boxing iterators to put them behind StorageProxy."
  • tensorflow/rust - "It would be most natural to provide an iterator over the records, but no efficient implementation is possible without GATS which I think I was hoping would already be in the language by now."

General themes for why folks want GATs

The general themes for why folks want GATs include...

  • GATs to avoid boxing/cloning (achieving a more performant, zero-copy API)
  • GATs to represent lifetime relationships that can't be expressed using regular associated types (e.g. fn(&self) -> Self::Gat<'_>)
    • Some of these get around it by using unsafe code which could be removed with GATs
  • GATs as a manual desugaring of RPITIT
  • GATs to offer a more abstract/pluggable API
  • GATs to provide a cleaner, DRY-er codebase