Skip to content

Commit

Permalink
Merge pull request #1265 from jackh726/gats
Browse files Browse the repository at this point in the history
Add basic GATs reference information
  • Loading branch information
nikomatsakis committed Oct 19, 2022
2 parents f6ed74f + 6b9e4ff commit 8104645
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 10 deletions.
145 changes: 140 additions & 5 deletions src/items/associated-items.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,22 +205,49 @@ types cannot be defined in [inherent implementations] nor can they be given a
default implementation in traits.

An *associated type declaration* declares a signature for associated type
definitions. It is written as `type`, then an [identifier], and
finally an optional list of trait bounds.
definitions. It is written in one of the following forms, where `Assoc` is the
name of the associated type, `Params` is a comma-separated list of type,
lifetime or const parameters, `Bounds` is a plus-separated list of trait bounds
that the associated type must meet, and `WhereBounds` is a comma-separated list
of bounds that the parameters must meet:

<!-- ignore: illustrative example forms -->
```rust,ignore
type Assoc;
type Assoc: Bounds;
type Assoc<Params>;
type Assoc<Params>: Bounds;
type Assoc<Params> where WhereBounds;
type Assoc<Params>: Bounds where WhereBounds;
```

The identifier is the name of the declared type alias. The optional trait bounds
must be fulfilled by the implementations of the type alias.
There is an implicit [`Sized`] bound on associated types that can be relaxed using the special `?Sized` bound.

An *associated type definition* defines a type alias on another type. It is
written as `type`, then an [identifier], then an `=`, and finally a [type].
An *associated type definition* defines a type alias for the implementation
of a trait on a type. They are written similarly to an *associated type declaration*,
but cannot contain `Bounds`, but instead must contain a `Type`:

<!-- ignore: illustrative example forms -->
```rust,ignore
type Assoc = Type;
type Assoc<Params> = Type; // the type `Type` here may reference `Params`
type Assoc<Params> = Type where WhereBounds;
type Assoc<Params> where WhereBounds = Type; // deprecated, prefer the form above
```

If a type `Item` has an associated type `Assoc` from a trait `Trait`, then
`<Item as Trait>::Assoc` is a type that is an alias of the type specified in the
associated type definition. Furthermore, if `Item` is a type parameter, then
`Item::Assoc` can be used in type parameters.

Associated types must not include [generic parameters] or [where clauses].
Associated types may include [generic parameters] and [where clauses]; these are
often referred to as *generic associated types*, or *GATs*. If the type `Thing`
has an associated type `Item` from a trait `Trait` with the generics `<'a>` , the
type can be named like `<Thing as Trait>::Item<'x>`, where `'x` is some lifetime
in scope. In this case, `'x` will be used wherever `'a` appears in the associated
type definitions on impls.

```rust
trait AssociatedType {
Expand Down Expand Up @@ -249,6 +276,37 @@ fn main() {
}
```

An example of associated types with generics and where clauses:

```rust
struct ArrayLender<'a, T>(&'a mut [T; 16]);

trait Lend {
// Generic associated type declaration
type Lender<'a> where Self: 'a;
fn lend<'a>(&'a mut self) -> Self::Lender<'a>;
}

impl<T> Lend for [T; 16] {
// Generic associated type definition
type Lender<'a> = ArrayLender<'a, T> where Self: 'a;

fn lend<'a>(&'a mut self) -> Self::Lender<'a> {
ArrayLender(self)
}
}

fn borrow<'a, T: Lend>(array: &'a mut T) -> <T as Lend>::Lender<'a> {
array.lend()
}


fn main() {
let mut array = [0usize; 16];
let lender = borrow(&mut array);
}
```

### Associated Types Container Example

Consider the following example of a `Container` trait. Notice that the type is
Expand Down Expand Up @@ -279,6 +337,83 @@ impl<T> Container for Vec<T> {
}
```

### Relationship between `Bounds` and `WhereBounds`

In this example:

```rust
# use std::fmt::Debug;
trait Example {
type Output<T>: Ord where T: Debug;
}
```

Given a reference to the associated type like `<X as Example>::Output<Y>`, the associated type itself must be `Ord`, and the type `Y` must be `Debug`.

### Required where clauses on generic associated types

Generic associated type declarations on traits currently may require a list of
where clauses, dependent on functions in the trait and how the GAT is used. These
rules may be loosened in the future; updates can be found [on the generic
associated types initiative repository](https://rust-lang.github.io/generic-associated-types-initiative/explainer/required_bounds.html).

In a few words, these where clauses are required in order to maximize the allowed
definitions of the associated type in impls. To do this, any clauses that *can be
proven to hold* on functions (using the parameters of the function or trait)
where a GAT appears as an input or output must also be written on the GAT itself.

```rust
trait LendingIterator {
type Item<'x> where Self: 'x;
fn next<'a>(&'a mut self) -> Self::Item<'a>;
}
```

In the above, on the `next` function, we can prove that `Self: 'a`, because of
the implied bounds from `&'a mut self`; therefore, we must write the equivalent
bound on the GAT itself: `where Self: 'x`.

When there are multiple functions in a trait that use the GAT, then the
*intersection* of the bounds from the different functions are used, rather than
the union.

```rust
trait Check<T> {
type Checker<'x>;
fn create_checker<'a>(item: &'a T) -> Self::Checker<'a>;
fn do_check(checker: Self::Checker<'_>);
}
```

In this example, no bounds are required on the `type Checker<'a>;`. While we
know that `T: 'a` on `create_checker`, we do not know that on `do_check`. However,
if `do_check` was commented out, then the `where T: 'x` bound would be required
on `Checker`.

The bounds on associated types also propagate required where clauses.

```rust
trait Iterable {
type Item<'a> where Self: 'a;
type Iterator<'a>: Iterator<Item = Self::Item<'a>> where Self: 'a;
fn iter<'a>(&'a self) -> Self::Iterator<'a>;
}
```

Here, `where Self: 'a` is required on `Item` because of `iter`. However, `Item`
is used in the bounds of `Iterator`, the `where Self: 'a` clause is also required
there.

Finally, any explicit uses of `'static` on GATs in the trait do not count towards
the required bounds.

```rust
trait StaticReturn {
type Y<'a>;
fn foo(&self) -> Self::Y<'static>;
}
```

## Associated Constants

*Associated constants* are [constants] associated with a type.
Expand Down
1 change: 1 addition & 0 deletions src/items/traits.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ Object safe traits can be the base trait of a [trait object]. A trait is
* All [supertraits] must also be object safe.
* `Sized` must not be a [supertrait][supertraits]. In other words, it must not require `Self: Sized`.
* It must not have any associated constants.
* It must not have any associated types with generics.
* All associated functions must either be dispatchable from a trait object or be explicitly non-dispatchable:
* Dispatchable functions require:
* Not have any type parameters (although lifetime parameters are allowed),
Expand Down
18 changes: 13 additions & 5 deletions src/items/type-aliases.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
> _TypeAlias_ :\
> &nbsp;&nbsp; `type` [IDENTIFIER]&nbsp;[_GenericParams_]<sup>?</sup>
> ( `:` [_TypeParamBounds_] )<sup>?</sup>
> [_WhereClause_]<sup>?</sup> ( `=` [_Type_] )<sup>?</sup> `;`
> [_WhereClause_]<sup>?</sup> ( `=` [_Type_] [_WhereClause_]<sup>?</sup>)<sup>?</sup> `;`
A _type alias_ defines a new name for an existing [type]. Type aliases are
declared with the keyword `type`. Every value has a single, specific type, but
Expand All @@ -31,11 +31,18 @@ let _ = UseAlias(5); // OK
let _ = TypeAlias(5); // Doesn't work
```

A type alias without the [_Type_] specification may only appear as an
[associated type] in a [trait].
A type alias, when not used as an associated type, must include a [_Type_] and
may not include [_TypeParamBounds_].

A type alias with [_TypeParamBounds_] may only specified when used as
an [associated type] in a [trait].
A type alias, when used as an [associated type] in a [trait], must not include a
[_Type_] specification but may include [_TypeParamBounds_].

A type alias, when used as an [associated type] in a [trait impl], must include
a [_Type_] specification and may not include [_TypeParamBounds_].

Where clauses before the equals sign on a type alias in a [trait impl] (like
`type TypeAlias<T> where T: Foo = Bar<T>`) are deprecated. Where clauses after
the equals sign (like `type TypeAlias<T> = Bar<T> where T: Foo`) are preferred.

[IDENTIFIER]: ../identifiers.md
[_GenericParams_]: generics.md
Expand All @@ -45,3 +52,4 @@ an [associated type] in a [trait].
[associated type]: associated-items.md#associated-types
[trait]: traits.md
[type]: ../types.md
[trait impl]: implementations.md#trait-implementations

0 comments on commit 8104645

Please sign in to comment.