BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage News Rust 1.65 Brings Generic Associated Types in a Step towards Higher-Kinded Types

Rust 1.65 Brings Generic Associated Types in a Step towards Higher-Kinded Types

The latest release of Rust introduces a powerful new language feature, called generic associated types, that allows developers to specify generics on associated types in traits. Other notable new features include the let-else statement, and support for breaking out of labeled blocks.

Over six years in the making, generic associated types (GATs) can be thought of as a form of type constructors on traits and can be used to define generic type, lifetime, or const generics on associated types.

This specific feature (associated type constructors) resolves one of the most common use cases for higher-kindedness, is a relatively simple extension to the type system compared to other forms of higher-kinded polymorphism, and is forward compatible with more complex forms of higher-kinded polymorphism that may be introduced in the future.

Associated types in Rust are a mechanism to defined generic traits. For example, the following example shows a Graph trait using two associated types for its nodes and edges:

trait Graph {
    type N;
    type E;

    fn has_edge(&self, _: &Self::N, _: &Self::N) -> bool;
    fn edges(&self, _: &Self::N) -> Vec<Self::E>
}

The use of associated types improves the readability of code and conveys the idea of a family of related types. A client of the Graph trait can indeed use it without the need to specify each time what its associated types are, as it would be required for a generic type. For example:

fn distance<C: Contains>(graph: &G, start: &G::N, end: &G::N) -> i32 { ... }

Now, GTAs introduce a way to specify an associated type that is itself generic. For example:

trait LendingIterator {
    type Item<'a> where Self: 'a;

    fn next<'a>(&'a mut self) -> Option<Self::Item<'a>>;
}

Compare this with the definition of the standard Iterator:

pub trait Iterator {
    type Item;
    ...
    fn next(&mut self) -> Option<Self::Item>
    ...
}

As you can see, LendingIterator uses Item<'a> instead of the non-generic Item associated type and constrains Self to be of type 'a. This means that the next function returns an item that borrows from self.

While somewhat arcane at first sight, GATs are a very powerful abstraction that is already used in its unstable incarnation by many crates. Additionally, a number of other crates were blocked in their further development by GATs not being stable. A few examples of the kind of features that can be built using GATs are zero-copy interfaces to load data from a DB, generic build patterns, representing non-owned values, and many more. It is also worth noting that in some cases GATs were replaced by the use of unsafe code and that GATs will reduce the need for unsafe code, where Rust stops providing any safety guarantees, in those cases.

As mentioned, GATs are not the only notable new feature in Rust 1.65. Specifically, the new let-else statement and early block termination using break are also worth a mention.

A let-else statement is an extension of a let that attempts to match a pattern and additionally provides and else block to be executed when there is no match, for example to return early from a function or panic.

Labeled breaks give full dignity to a behavior that was already achieved using the workaround of loop blocks that execute just once, due to the fact that loop blocks support the possibility of arbitrarily breaking out of the loop. Now, you can also label a block and then use the break <block-label> statement to jump at the end of that labeled block.

If you are interested in the full details about changes in Rust 1.65, check the official release notes.

About the Author

Rate this Article

Adoption
Style

BT