Generic Associated Types initiative

initiative status: active

What is this?

This page tracks the work of the Generic Associated Types initiative! To learn more about what we are trying to do, and to find out the people who are doing it, take a look at the charter.

Current status

The following table lists of the stages of an initiative, along with links to the artifacts that will be produced by the start of that stage.

StageStateArtifact(s)
ProposalCharter
Tracking issue
ExperimentalRFC
Development🦀Explainer
Feature complete💤Stabilization report
Stabilized💤

Key:

  • ✅ -- phase complete
  • 🦀 -- phase in progress
  • 💤 -- phase not started yet

How Can I Get Involved?

  • Check for "help wanted" issues on this repository!
  • If you would like to help with development, please contact the owner to find out if there are things that need doing.
  • If you would like to help with the design, check the list of active design questions first.
  • If you have questions about the design, you can file an issue, but be sure to check the FAQ or the design-questions first to see if there is already something that covers your topic.
  • If you are using the feature and would like to provide feedback about your experiences, please [open a "experience report" issue].
  • If you are using the feature and would like to report a bug, please open a regular issue.

We also participate on Zulip, feel free to introduce yourself over there and ask us any questions you have.

Building Documentation

This repository is also an mdbook project. You can view and build it using the following command.

mdbook serve

✏️ Updates

Lang-team initiatives give monthly updates. This section collects the updates from this initiative for posterity.

2021-Oct: Lang team update

Summary

  • GATs are moving towards stabilization. Major blockers:
    • Resolving the default inference question we discussed earlier (plan described below)
    • Resolving the syntax of where clauses
    • Explainer that covers common usage patterns
    • Documenting test coverage
    • Triage known bugs
  • Since we landed various improvements, jackh726 has been monitoring bugs, many of them are general issues with rustc that come up somewhat more often for GATs

Goals for this month

  • Implement default inference solution
  • Settle question of whre clause syntax
  • Finish explainer and issue triage

Notes

The general plan is to push Generated Associated types towards stabilization, but with the expectation that the current form has ergonomic pitfalls. You can think of it as an MVP to expose functionality. Towards that end we aim to write an explainer that highlights the current state of the feature, what it can do, and what it can't.

Resolving the question of where clause defaults

Last month we discussed a potential defaulting scheme for where Self: 'a annotations. There were some unknowns, such as the "false default" rate -- i.e., cases where the default would trigger but you don't want it. To help gain more data, the plan is to implement the logic for adding a default, but instead of adding a default, we will require that the default be written explicitly. You can think of it as an extended "well-formedness" requirement.

This preserves future flexibility:

  • Remove the error altogether
  • Add as a lint with a fix
  • Simply add the where clause by default, perhaps with an opt out

The error message for this default will include instructions on (a) known workarounds if it is not desired and (b) an issue where folks can comment if they have codebases where the default was not helpful. This should enable us to gain some data.

"Generic Associated Types" Charter

Goals

Membership

RoleGithub
Ownerjackh726
Liaisonnikomatsakis

📚 Explainer

The "explainer" is "end-user readable" documentation that explains how to use the feature being deveoped by this initiative. If you want to experiment with the feature, you've come to the right place. Until the feature enters "feature complete" form, the explainer should be considered a work-in-progress.

⚠️ Suboptimal syntax alert ⚠️

In general, the GAT feature is currently in a "minimum viable product" phase. That means that there are a number of places where the syntax is kind of clumsy, and more annotations are required than we would like to have. The explainer describes the feature that we are aiming to stabilize but you can see the Future Directions section for some notes on the kinds of changes we would like to make.

The problem in a nutshell

If you want to iterate over something in Rust, you might write the following:


#![allow(unused)]
fn main() {
let v = vec![1, 2, 3];
for elem in v.iter() { ... }
}

The iter method returns an iterator of references into the items in the vector. Conceptually, it looks something like this (the actual implementation works differently):


#![allow(unused)]
fn main() {
impl<T> Vec<T> {
    fn iter<'a>(&'a self) -> Iter<'a, T> {
        Iter { vec: self, position: 0 }
    }
}
}

where Iter is a struct that implements the Iterator trait:


#![allow(unused)]
fn main() {
pub struct Iter<'a, T> {
    vec: &'a Vec<T>,
    position: usize,
}

impl<'a, T> Iterator for Iter<'a, T> {
    type Item = &'a T;

    fn next(&mut self) -> Option<&'a T> {
        let i = self.position;
        if i < self.vec.len() {
            self.position += 1;
            Some(&self.vec[i])
        } else {
            None
        }
    }
}
}

Rust's other collections also have similar iter methods that work the same way. You might wonder, why not have an Iterable trait, that encapsulates this pattern? Something like this:


#![allow(unused)]
fn main() {
trait Iterable {
    type Item;
    type Iterator: Iterator<Item = Self::Item>;

    fn iter<'a>(&'a self) -> Self::Iterator;
}
}

Well, if you look closely at this trait, you'll see that it doesn't work. The type that an impl needs to provide for Iterator needs to be able to reference 'a, the lifetime of the self parameter. But currently, it can't! That lifetime is not in scope:


#![allow(unused)]
fn main() {
impl<T> Iterable for Vec<T> {
    type Item = &'a T;
    //           ^^ 'a is not in scope here!
    type Iterator = Iter<'a, T>;
    //                   ^^ 'a is not in scope here!

    fn iter<'a>(&'a self) -> Iter<'a, T> {
        Iter { vec: self, position: 0 }
    }
}
}

Enter: generic associated types

With generic associated types, associated types can have generic parameters. This makes it possible to model the Iterable trait by having the Iterator type take a generic parameter, 'a:


#![allow(unused)]
fn main() {
trait Iterable {
    type Item<'a>
    where
        Self: 'a; // <-- see the "required bounds" section for more information

    type Iterator<'a>: Iterator<Item = Self::Item<'a>>
    where
        Self: 'a; // <-- see the "required bounds" section for more information

    fn iter<'a>(&'a self) -> Self::Iterator<'a>;
}
}

Given this trait definition, we can implement Iterable like so:


#![allow(unused)]
fn main() {
impl<T> Iterable for Vec<T> {
    type Item<'a> = &'a T
    where
        T: 'a; // <-- see the "required bounds" section for more information

    type Iterator<'a> = Iter<'a, T>
    where
        T: 'a; // <-- see the "required bounds" section for more information

    fn iter<'a>(&'a self) -> Iter<'a, T> {
        Iter { vec: self, position: 0 }
    }
}
}

Using GATs

Suppose I want to write a function that accepts any iterable and creates a vector by cloning its elements. I can now write such a thing like this:


#![allow(unused)]
fn main() {
fn into_vec<T>(
    iterable: &impl for<'a> Iterable<Item<'a> = &'a T>,
) -> Vec<T>
where
    T: Clone
{
    let mut out = vec![];
    for elem in iterable.iter() {
        out.push(elem.clone());
    }
    out
}
}

Let's look at this function more closely. The most interesting part is the type of the iterable parameter:


#![allow(unused)]
fn main() {
iterable: &impl for<'a> Iterable<Item<'a> = &'a T>,
//              ^^^^^^^          ^^^^^^^^
}

This admittedly verbose syntax is a way of saying:

  • iterable is some kind of Iterable that, when iterated with some lifetime 'a, yields up values of type &'a T.

The for<'a> binder is a way of making this bound apply for any lifetime, rather than talking about some specific lifetime.

Applying GATs to a specific lifetime

The previous example showed an iterable applied to any lifetime. It is also possible to give bounds for some specific lifetime. This function, for example, takes an iterable with lifetime 'i and yields up the first element:


#![allow(unused)]
fn main() {
fn first<'i, T>(
    iterable: &'i impl Iterable<Item<'i> = &'i T>,
) -> Option<&'i T>
{
    iterable.iter().next()
}
}

The bound impl Iterable<Item<'i> = &'i T> says "when iterated with lifetime 'i, the resulting reference is &'i T".

Required bounds

nightly seeking-feedback

We are actively soliciting feedback on the design of this aspect of GATs. This section explains the current nightly behavior, but at the end there is note about the behavior we expect to adopt in the future.

A common use for GATs is to represent the lifetime of data that is borrowed from a value of type Self, or some other parameter. Consider the iter method of the Iterable trait:


#![allow(unused)]
fn main() {
trait Iterable {
    ...

    fn iter<'a>(&'a self) -> Self::Iterator<'a>;
}
}

Here, the argument 'a that is given to Self::Iterator represents the lifetime of the self reference. However, by default, there is nothing in the definition of the Iterator associated type that links its lifetime argument and the Self type:


#![allow(unused)]
fn main() {
// Warning: For reasons we are in the midst of explaining,
// this version of the trait will not compile.
trait Iterable {
    type Item<'me>;

    type Iterator<'me>: Iterator<Item = Self::Item<'me>>;

    fn iter<'a>(&'a self) -> Self::Iterator<'a>;
}
}

If you try to compile this trait, you will find that you get an error. To make it compile, you have to indicate the link between 'me and Self by adding where Self: 'me. This outlives bound indicates the GATs can only be used with a lifetime 'me that could legally be used to borrow Self. This version compiles:


#![allow(unused)]
fn main() {
trait Iterable {
    type Item<'me>
    where
        Self: 'me;

    type Iterator<'me>: Iterator<Item = Self::Item<'me>>
    where
        Self: 'me;

    fn iter<'a>(&'a self) -> Self::Iterator<'a>;
}
}

Why are these bounds required?

Without these bounds, users of the trait would almost certainly not be able to write the impls that they need to write. Consider an implementation of Iterable for Vec<T>, assuming that there are no where Self: 'me bounds on the GATs:


#![allow(unused)]
fn main() {
impl Iterable for Vec<T> {
    type Item<'me> = &'me T;
    type Iterator<'me> = std::vec::Iter<'me, T>;
    fn iter(&self) -> Self::Iterator<'_> { self.iter() }
}
}

The problem comes from the associated types. Consider the type Item<'me> = &'me T declaration, for example: for the type &'me T to be legal, we must know that T: 'me. Otherwise, nothing stops us from using Self::Item<'static> to construct a reference with a lietime 'static that may outlive its referent T, and that can lead to unsoundness in the type system. In the case of the iter method, the fact that it takes a parameter self of type &'me Vec<T> already implies that T: 'me (otherwise that parameter would have an invalid type). However, that doesn't apply to the GAT Item. This is why the associated types need a where clause:


#![allow(unused)]
fn main() {
impl Iterable for Vec<T> {
    type Item<'me> = &'me T where Self: 'me;
    type Iterator<'me> = std::vec::Iter<'me, T> where Self: 'me;
    fn iter(&self) -> Self::Iterator<'_> { self.iter() }
}
}

However, this impl is not legal unless the trait also has a where Self: 'me requirement. Otherwise, the impl has more where clauses than the trait, and that causes a problem for generic users that don't know which impl they are using.

Precise rules

The precise rules that the compiler uses to decide when to issue an error are as follows:

  • For every GAT G in a trait definition with generic parameters X0...Xn from the trait and Xn..Xm on the GAT... (e.g., Item or Iterable, in the case of Iterable, with generic parameters [Self] from the trait and ['me] from the GAT)
    • If for every method in the trait... (e.g., iter, in the case of Iterable)
      • When the method signature (argument types, return type, where clauses) references G like <P0 as Trait<P1..Pn>>::G<Pn..Pm> (e.g., <Self as Iterable>::Iterator<'a>, in the iter method, where P0 = Self and P1 = 'a)...
        • we can show that Pi: Pj for two parameters on the reference to G (e.g., Self: 'a, in our example)
          • then the GAT must have Xi: Xj in its where clause list in the trait (e.g., Self: 'me).

Plan going forward: Add rules by default

In the future, rather than reporting an error in cases like this, we expect to add these where clauses to the trait and impl by default. However, there is some possibility that this will rule out legal impls for which the where clause might not be necessary. This can happen if either (a) the lifetime, despite being linked to a type like Self in the methods, is not used to borrow content from Self; or (b) in all impls of this trait that could exist, the types being borrowed don't have references and are known to be 'static.

Feedback requested!

The current compiler adds the future defaults as a hard error precisely so that we can get the attention of early users and find out if these where clauses pose any kind of problem. If you are finding that you have a trait and impls that you believe would compile fine, but doesn't because of these where clauses, then we would like to hear from you! Please file an issue on this repository, and use the "Feedback on required bounds" template.

Workaround

If you find that this requirement is causing you a problem, there is a workaround. You can refactor your trait into two traits. For example, to write a version of Iterable that doesn't require where Self: 'me, you might do the following:


#![allow(unused)]
fn main() {
trait IterableTypes {
    type Item<'me>;
    type Iterator<'me>: Iterator<Item = Self::Item<'me>>;
}

trait Iterable: IterableTypes {
    fn iter<'a>(&'a self) -> Self::Iterator<'a>;
}
}

This is a bit heavy-handed, but there's a logic to it: the rules are geared around ensuring that the associated types and methods that appear together in a single trait will work well together. By separating the associated types from the function into distinct traits, you are effectively asserting that the associated types are meant to be used independently from the function and hence it doesn't necessarily make sense to have the where clauses derived from the method signature.

Future directions

There are some points about GATs which we expect to change. Deciding on the best solution, however, requires more information about the way GATs are used in practice. Therefore, we encourage you to [file a new issue] sharing your experiences and giving feedback on these points.

[open issues]: [file an issue]: https://github.com/rust-lang/generic-associated-types-initiative/issues/new/choose

Proposal A: Supply required bounds by default

The first planned change is to make the required bounds on associated types be supplied by default, rather than requiring that they be typed explicitly. We may or may not include some form of annotation to "opt-out" from this default. In short, the expectation is that -- in practice -- virtually all traits will want these where clauses, and there will be negligibly few cases that do not. We are therefore looking for examples where having to add the required bounds was a problem, and you were forced to adopt the workaround.

✨ RFC

RFC #1598

Design discussions

This directory hosts notes on important design discussions along with their resolutions. In the table of contents, you will find the overall status:

  • ✅ -- Settled! Input only needed if you have identified a fresh consideration that is not covered by the write-up.
  • 💬 -- Under active discussion. Check the write-up, which may contain a list of questions or places where feedback is desired.
  • 💤 -- Paused. Not under active discussion, but we may be updating the write-up from time to time with details.

Outlives defaults

Summary

We are moving towards stabilizing GATs (tracking issue: #44265) but there is one major ergonomic hurdle that we should decide how to manage before we go forward. In particular, a great many GAT use cases require a surprising where clause to be well-typed; this typically has the form where Self: 'a. In "English", this states that the GAT can only be used with some lifetime 'a that could've been used to borrow the Self type. This is because GATs are frequently used to return data owned by the Self type. It might be useful if we were to create some rules to add this rule by default. Once we stabilize, changing defaults will be more difficult, and could require an edition, therefore it's better to evaluate the rules now.

I have an opinion! What should I do?

To make this decision in an informed way, what we need most are real-world examples and experience reports. If you are experimenting with GATs, for example, how often do you use where Self: 'a and how did you find out that it is necessary? Would the default proposals described below work for you? If not, can you describe the trait so we can understand why they would not work?

Of great use would be example usages that do NOT require where Self: 'a. It'd be good to be able to evaluate the various defaulting schemes and see whether they would interfere with the trait. Knowing the trait and a rough sketch of the impls would be helpful.

Background: what where clause now?

Consider the typical "lending iterator" example. The idea here is to have an iterator that produces values that may have references into the iterator itself (as opposed to references into the collection being iterated over). In other words, given a next method like fn next<'a>(&'a mut self), the returned items have to be able to reference 'a. The typical Iterator trait cannot express that, but GATs can:


#![allow(unused)]
fn main() {
trait LendingIterator {
    type Item<'a>;

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

Unfortunately, this trait definition turns out to be not quite right in practice. Consider an example like this, an iterator that yields a reference to the same item over and over again (note that it owns the item it is referencing):


#![allow(unused)]
fn main() {
struct RefOnce<T> {
    my_data: T    
}

impl<T> LendingIterator for RefOnce<T> {
    type Item<'a> where Self: 'a = &'a T;

    fn next<'b>(&'b mut self) -> Self::Item<'b> {
        &self.my_data
    }
}
}

Here, the type type Item<'a> = &'a T declaration is actually illegal. Why is that? The assumption when authoring the trait was that 'a would always be the lifetime of the self reference in the next function, of course, but that is not in fact required. People can reference Item with any lifetime they want. For example, what if somebody wrote the type <SomeType<T> as LendingIterator>::Item<'static>? In this case, T: 'static would have to be true, but T may in fact contain borrowed references. This is why the compiler gives you a "T may not outlive 'a" error (playground).

We can encode the constraint that "'a is meant to be the lifetime of the self reference" by adding a where Self: 'a clause to the type Item declaration. This is saying "you can only use a 'a that could be a reference to Self". If you make this change, you'll find that the code compiles (playground):


#![allow(unused)]
fn main() {
trait LendingIterator {
    type Item<'a> where Self: 'a;

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

When would you NOT want the where clause Self: 'a?

If the associated type cannot refer to data that comes from the Self type, then the where Self: 'a is unnecessary, and is in fact somewhat constraining.

Example: Output doesn't borrow from Self

In the Parser trait, the Output does not ultimately contain data borrowed from self:


#![allow(unused)]
fn main() {
trait Parser {
    type Output<'a>;
    fn parse<'a>(&mut self, data: &'a [u8]) -> Self::Output<'a>;
}
}

If you were to implement Parser for some reference type (in this case, &'b Dummy) you can now set Output to something that has no relation to 'b:


#![allow(unused)]
fn main() {
impl<'b> Parser for &'b Dummy {
    type Output<'a> = &'a [u8];

    fn parse<'a>(&mut self, data: &'a [u8]) -> Self::Output<'a> {
        data 
    }
}
}

Note that you would need a similar where clause if you were going to have a setup like:


#![allow(unused)]
fn main() {
trait Transform<Input> {
    type Output<'a>
    where
        Input: 'i;

    fn transform<'i>(&mut self: &'i Input) -> Self::Output<'i>;
}
}

Example: Iter static

In the previous example, the lifetime parameter for Output was not related to the self parameter. Are there (realistic) examples where the associated type is applied to the lifetime parameter from self but the where Self: 'a is not desired?

There are some, but they rely on having "special knowledge" of the types that will be used in the impl, and they don't seem especially realistic. The reason is that, if you have a GAT with a lifetime parameter, it is likely that the GAT contains some data borrowed for that lifetime! But if you use the lifetime of self, that implies we are borrowing some data from self -- however, it doesn't necessarily imply that we are borrowing data of any particular type. Consider this example:


#![allow(unused)]
fn main() {
trait Message {
    type Data<'a>: Display;

    fn data<'b>(&'b mut self) -> Self::Data<'b>;

    fn default() -> Self::Data<'static>;
}

struct MyMessage<T> {
    text: String,
    payload: T,
}

impl<T> Message for MyMessage<T> {
    type Data<'a>: Display = &'a str;
    // No requirement that `T: 'a`!

    fn data<'b>(&'b mut self) -> Self::Data<'b> {
        // In here, we know that `T: 'b`
    }

    fn default() -> Self::Data<'static> {
        "Hello, world"        
    }
}
}

Here the where T: 'a requirement is not necessary, and may in fact be annoying when invoking <MyMessage<T> as Message>::default() (as it would then require that T: 'static).

Another possibility is that the usage of <MyMessage<T> as Message>::Data<'static> doesn't appear inside the trait definition, although it is hard to imagine exactly how one writes a useful function like that in practice.

Alternatives

Status quo

We ship with no default. This kind of locks in a box, because adding a default later would be a breaking change to existing impls that are affected by the default. since some of them may be using the associated types with a lifetime unrelated to Self. Note though that a sufficiently tailored default would only break code that was going to -- or perhaps very likely to -- not compile anyhow.

Smart default: add where Self: 'a if the GAT is used with the lifetime from &self (and extend to other type parameters)

Analyze the types of methods within the trait definition. It a GAT is applied to a lifetime 'x, examine the implied bounds of the method for bounds of the form T: 'x, where T is an input parameter to the trait. If we find such bounds on all methods for every use of the GAT, then add the corresponding default.

Consider the LendingIterator trait:


#![allow(unused)]
fn main() {
trait LendingIterator {
    type Item<'a>;

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

Analyzing the closure body, we see that it contains Self::Item<'b> where 'b is the lifetime of the self reference (e.g., self: &'b Self or self: &'b mut Self). The implied bounds of this method contain Self: 'b. Since there is only one use of Self::Item<'b>, and the implied bound Self: 'b applies in that case, then we add the default where Self: 'a to the GAT.

This check is a fairly simple syntactic check, though not necessarily easy to explain. It would accept all the examples that appear in this document, including the example with fn default() -> Self::Data<'static> (in that case, the default is not triggered, because we found a use of Data that is applied to a lifetime for which no implied bound applies). The only case where this default behaves incorrectly is the case where all uses of Self::Data that appear within the trait need the default, but there are uses outside the trait that do not (I couldn't come up with a realistic example of how to do this usefully).

Extending to other type parameters

The inference can be extended naturally beyond self to other type parameters. Therefore this example:


#![allow(unused)]
fn main() {
trait Parser<Input> {
    type Output<'i>;

    fn get<'input>(&mut self, i: &'input Input) -> Self::Output<'input>;
}
}

would infer a where Input: 'i bound on type Output<'i>.

Similarly:


#![allow(unused)]
fn main() {
trait Parser<Input> {
    type Output<'i>;

    fn get(&mut self, i: &'input Input) -> Self::Output<'input>;
}
}

would infer a where Input: 'i bound on type Output<'i>.

Avoiding the default

If this default is truly not desired, there is a workaround: one can declare a supertrait that contains just the associated type. For example:


#![allow(unused)]
fn main() {
trait IterType {
    type Iter<'b>;
}

trait LendingIterator: IterType {
    fn next(&mut self) -> Self::Iter<'_>;
}
}

This workaround is not especially obvious, however.

We used to require T: 'a bounds in structs:


#![allow(unused)]
fn main() {
struct Foo<'a, T> {
    x: &'a T
}
}

but as of RFC 2093 we infer such bounds from the fields in the struct body. In this case, if we do come up with a default rule, we are essentially inferring the presence of such bounds by usages of the associated type within the trait definition.

Recommendation

Niko's recommendation is to use the "smart defaults". Why? They basically always do the right thing, thus contributing to supportive, at the cost of (theoretical) versatility. This seems like the right trade-off to me.

The counterargument would be: the rules are sufficiently complex, we can potentially add this later, and people are going to be surprised by this default when it "goes wrong" for them. It would be hard, but not impossible, to add a tailored error message for cases where the where T: 'b check fails.

Not sure about Jack's opinion. =)

Appendix A: Ruled out alternatives

Special syntax

We could use the 'self "keyword", permitted only in GATs, to indicate "a lifetime with the where clause where Self: 'self". The LendingIterator trait would therefore be written


#![allow(unused)]
fn main() {
trait LendingIterator {
    type Item<'self>;

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

Forwards compatibility note: This option could be added later; note also that 'self is not currently valid.

Why not? 'self is an awfully suggestive syntax. It may be useful for things like self-referential structs. This just doesn't important enough.

Force people to write where Self: 'a

To buy time, we could force people to write where Self: 'a, so that we can later allow it to be elided. This unfortunately would eliminate a number of valid use cases for GATs (though they would later be supported).

Why not? Rules out a number of useful cases.

Dumb default: Always default to where Self: 'a

The most obvious default is to add where Self: 'a to the where clause list for any GAT with a lifetime parameter 'a, but that seems too crude. It will rule out all existing cases unless we add some form of "opt-out" syntax, for which we have no real precedent.

Why not? Rules out a number of useful cases.

Appendix B: Considerations

Appendix C: Other examples

Example: Ruma


#![allow(unused)]
fn main() {
// Every endpoint in the Matrix REST API has two request and response types in Ruma, one Incoming
// (used for deserialization) and out Outgoing (used for serialization). To avoid annoying clones when
// sending a request, most non-copy fields in the outgoing structs are references.
//
// The request traits have an associated type for the corresponding response type so things can be
// matched up properly.
pub trait IncomingRequest: Sized {
    // This is the current definition of the associated type I'd like to make generic.
    type OutgoingResponse: OutgoingResponse;
    // AFAICT adding a lifetime parameter is all I need.
    type OutgoingResponse<'a>: OutgoingResponse;

    // Other trait members... (not using Self::OutgoingResponse)
}
}

full definition

💬 Where does the where clause go?

This is write-up of the conclusion to the [where does the where clause go?] question. To read more background, see the links section below.

Conclusion

Where clauses in generic associated types comes after value/binding

Example:


#![allow(unused)]
fn main() {
trait MyTrait {
    type MyType<T>: Iterator
    where
        T: Ord;
}
    
impl MyTrait for MyOtherType {
    type MyType<T> = MyIterator
    where
        T: Ord;
}
}

Effectively the = type in the impl replaces the : Bound from the declaration with the value that has to meet those bounds.

Later phase: type aliases

Type aliases will eventually be aligned to this syntax as well:


#![allow(unused)]
fn main() {
type MyType<T> = Vec<T> where T: Ord;
}

Currently, however, where clauses on type aliases are ignored, so we will not stabilize this new syntax until they have the meaning we want.

Suggestions for users who put the where clause in the wrong place

We will parse where clauses in both positions and suggest to users that they be moved:


#![allow(unused)]
fn main() {
impl MyTrait for MyOtherType {
    type MyType<T>
    where
        T: Ord
    = MyIterator;
}
}

Gets an error with a suggested rewrite. The compiler proceeds "as if" the where clauses had been written after the = Type.

Where clause syntax for trait aliases will have to be revisited

As described in the FAQ below, we currently support trait alias syntax like


#![allow(unused)]
fn main() {
trait ReverseEq<T> = where T: PartialEq<Self>;
}

This syntax will be removed. Although its capabilities could be useful, it is also quite confusing (the placement of the where is a subtle distinction), and not clearly needed. If we find that we do want it, we can add in a similar syntax later, but hopefully in a way that is more broadly consistent with the language.

Discussion and FAQ

But isn't it inconsistent with other trait items to put the where clauses before the =?

From one perspective, yes. One can view the value of an associated type as its "body", and the where clauses typically come before the "body" of an item. Put another way, typically you can "copy and paste" the impl and then add some text to the end of each item to specify its value: but with this syntax, you have to edit the "middle" of an associated type to specify its value.

The analogy of an associated type value to a function body, however, is somewhat flawed. The value of an associated type needs to be considered part of the "signature", or public facing, part of the impl. Consider: you can change the body of a function and be certain that your callees will still compile, but you cannot do the same for the value of an associated type.

Given this perspective, when you copy the associated type from the trait to the impl, you are "completing" the signature that was left incomplete by the trait. Moreover, to do so, you replace the : Bound1 + Bound2 list (which constraints what kinds of types the impl might use) with a specific type, thus making it more specific.

What about a more purely syntactic point-of-view? What is more consistent?

There is precedent that the placement of the where clause has less to do with the logical role that it plays and more to do with other factors, like whether it is followed by a braced list of items:

  • With struct Foo<T> where T: Ord { t: T }, the "body" of the struct is its fields, and the where clause comes first.
  • But we write struct Foo<T>(T) where T: Ord, thus placing the "body" (the fields (T)) first and the where clause second. Moreover, we initially implemented the grammar struct Foo<T> where T: Ord (T) but this was deemed so obviously confusing that it was changed with little discussion.

As further evidence that this syntax is inconsistent with Rust's traditions, placing the where clauses before the = ty makes it objectively hard to determine how to run rustfmt in a way that feels natural. rustfmt handles where by putting the where onto its own line, with one line per where clause. This structure works for existing Rust items because where clauses are always either following by nothing (tuple structs) or by a braced ({}) list of items (e.g., struct fields, fn body, etc). That opening { can therefore go on its own line. This where clause formatting does not work well with =.

The idea of having where clauses come at the "end" of the signature is also supported by the original RFC, which motivated where clauses in part by describing how they allow you to treat the precise bounds as "secondary" to the "important stuff":

If I may step aside from the "impersonal voice" of the RFC for a moment, I personally find that when writing generic code it is helpful to focus on the types and signatures, and come to the bounds later. Where clauses help to separate these distinctions. Naturally, your mileage may vary. - nmatsakis

In the case of an impl specifying the value for an associated type, the "important stuff" the value of the associated type.

What about trait aliases, don't they distinguish where clause placement?

As currently implemented, trait aliases have two distinct possible placements for where clauses, which effectively distinguishes between a where clause (which must be proven true in order to use the alias) and an implied bound (which is part of what the alias expands to). One can write:


#![allow(unused)]
fn main() {
trait Foo<T: Debug> = Bar<T> + Baz<T>
}

in which case where X: Foo<Y> is only legal if Y: Debug is known from some other place. This is roughly equivalent to a trait like so:


#![allow(unused)]
fn main() {
trait Foo1<T: Debug>: Bar<T> + Baz<T> { }
}

The clause where X: Foo1<Y> is also only valid when Y: Debug is known. This is in contrast to the "supertraits" Bar<Y> and Baz<Y>, which are implied by X: Foo1<Y> ("supertraits" are also sometimes called "implied bounds").

Alternatively, one can include the where clause in the "value" of the trait alias like so:


#![allow(unused)]
fn main() {
trait ReverseEq<T> = where T: PartialEq<Self>;
}

In this case, where X: ReverseEq<Y> is equivalent to Y: PartialEq<X>. There is no "equivalent trait" for usage like this; the T: PartialEq<Self> effectively acts like a supertrait or implied bound.

Our decision was that this is a subtle distinction and that using the placement of the where clause was not a great way to make it.

Is that trait alias syntax consistent with the rest of the language?

Not really. There are other places in the language that could benefit from a similar flexibility around implied bounds. For example, one could imagine wanting to have an associated type T::MyType<Y> where it is known that Y: PartialEq<T::MyType<Y>>, but this cannot be readily written with today's syntax:


#![allow(unused)]
fn main() {
trait MyTrait {
    type MyType<T>: PartialEq<T>;
    //              ^^^^^^^^^ not what we wanted
}
}

We decided that if we were going to offer that capability, we should find a way to offer it more generally, and hopefully with more clarity than putting the where clause before or after the =. As we have seen, where clauses for different kinds of items can be rather variable in their placement, so it is not clear that all users will recognize that distinction and understand it (many present in the meeting were surprised by the distinction as well).

Alternatively, the implied bounds proposal goes another way, turning most where clauses into implied bounds by default!

Why do you even need where clauses in the impl anyway?

Given that the where clauses appear in the trait, you might wonder why they are needed in the impl anyway. After all, the impl could just assume that the trait bounds are met when checking the value of the associated type for validity, making the whole issue moot.

This would however be inconsistent with other sorts of items, which do require users to copy over the bounds from the trait. Furthermore, we have discussed the idea of allowing impls to relax the bounds from those described in the trait if they are not needed in the impl -- this came up most recently in the context of allowing impls to forego the unsafe keyword for unsafe fn declared in the trait if the fn in the impl body is completely safe. This could then even be relied upon by people invoking the method who know the precise impl they will be using.

In short, this might be a reasonable choice to make, but we should make it uniformly, and it shuts down the direction of using the lack of bounds in the impl as a kind of signal.

Why not change type alias notation too?

Top-level type aliases currently parse with the where clause before the =:


#![allow(unused)]
fn main() {
type Foo<T> where T: Ord = T;
}

If you try that above example, however, you will find that you get a warning: this is because the where T: Ord is completely ignored! This is an implementation limitation in the way the current compiler eagerly expands type aliases. Moving the placement of where clauses actually gives us an opportunity to change this behavior without breaking any existing code, which is nice. It will however require some kind of opt-in (such as a cargo fix run) to migrate existing code that uses where clauses in the "ignored place" to the new format.

Where does the where clause go?

UPDATE

This document is retained for historical purposes. See the Where the Where conclusion for the most up-to-date conversation.

Summary

Proposed: to alter the syntax of where clauses on type aliases so that they appear after the value:

type StringMap<K> = BTreeMap<K, String>
where
    K: PartialOrd

This applies both in top-level modules and in trats (associated types, generic or otherwise).

Background

The current syntax for where to place the "where clause" of a generic associated types is awkward. Consider this example (playground):


#![allow(unused)]
fn main() {
trait Iterable {
    type Iter<'a> where Self: 'a;

    fn iter(&self) -> Self::Iter<'_>;
}

impl<T> Iterable for Vec<T> {
    type Iter<'a>
    where 
        Self: 'a = <&'a [T] as IntoIterator>::IntoIter;

    fn iter(&self) -> Self::Iter<'_> {
        self.iter()
    }
}
}

Note the impl. Most people expect the impl to be written as follows (indeed, the author wrote it this way in the first draft):


#![allow(unused)]
fn main() {
impl Iterable for Vec<T> {
    type Iter<'a>  = <&'a [T] as Iterator>::Iter
    where 
        Self: 'a;

    fn iter(&self) -> Self::Iter<'_> {
        self.iter()
    }
}
}

However, this placement of the where clause is in fact rather inconsistent, since the = <&'a [T] as Iterator>::Iter is in some sense the "body" of the item.

The same current syntax is used for where clauses on type aliases (playground):

type Foo<T> where T: Eq = Vec<T>;

fn main() { }

Top-level type aliases

Currently, we accept where clauses in top-level type aliases, but they are deprecated (warning) and semi-ignored:

type StringMap<K> where
    K: PartialOrd
= BTreeMap<K, String>

Under this proposal, this syntax remains, but is deprecated. The newer syntax for type aliases (with where coming after the type) would remain feature gated until such time as we enforce the expected semantics.

Alternatives

Keep the current syntax.

In this case, we must settle the question of how we expect it to be formatted (surely not as I have shown it above).


#![allow(unused)]
fn main() {
impl<T> Iterable for Vec<T> {
    type Iter<'a> where Self: 'a 
        = <&'a [T] as IntoIterator>::IntoIter;

    fn iter(&self) -> Self::Iter<'_> {
        self.iter()
    }
}
}

Accept either

What do we do if both are supplied?

😕 FAQ