Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic GATs reference information #1265

Merged
merged 11 commits into from
Oct 19, 2022
128 changes: 123 additions & 5 deletions src/items/associated-items.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,22 +205,47 @@ 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
on the associated type, and `WhereBounds` is a comma-separated list of bounds on
parameters:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
lifetime or const parameters, `Bounds` is a plus-separated list of trait bounds
on the associated type, and `WhereBounds` is a comma-separated list of bounds on
parameters:
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:


```rust,ignore
jackh726 marked this conversation as resolved.
Show resolved Hide resolved
type Assoc;
type Assoc: Bounds;
type Assoc<Params>;
type Assoc<Params>: Bounds;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It occurs to me that it would be worth clarifying the distinction between Bounds and WhereBounds!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is stated in the paragraph above? Should we be more detailed?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could be more explicit. Left some suggestions. WDYT?

type Assoc<Params> where WhereBounds;
type Assoc<Params>: Bounds where WhereBounds;
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe something like

Relationship between Bounds and WhereBounds

In this example:

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.


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 on for the implementation
jackh726 marked this conversation as resolved.
Show resolved Hide resolved
nikomatsakis marked this conversation as resolved.
Show resolved Hide resolved
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`:

```rust,ignore
jackh726 marked this conversation as resolved.
Show resolved Hide resolved
type Assoc = Type;
type Assoc<Params> = Type<Params>;
nikomatsakis marked this conversation as resolved.
Show resolved Hide resolved
type Assoc<Params> where WhereBounds = Type;
type Assoc<Params> = Type where WhereBounds;
jackh726 marked this conversation as resolved.
Show resolved Hide resolved
```

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 +274,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 +335,68 @@ impl<T> Container for Vec<T> {
}
```

### 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.
nikomatsakis marked this conversation as resolved.
Show resolved Hide resolved

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
10 changes: 9 additions & 1 deletion 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 Down Expand Up @@ -34,9 +34,16 @@ 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 with the [_Type_] specification may only appear as an
[associated type] in a [trait impl].
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This clause by itself doesn't seem to be correct to me. It seems to imply that the following is not allowed:

type X = i32;

Perhaps this could be turned around so that it specifically says what a trait impl associated type needs? A sentence could be added to the previous paragraph, since they are talking about the same thing. Maybe something like this:

A type alias without the [Type] specification may only appear as an [associated type] in a [trait].
Associated types in a [trait impl] must include the [Type] specification.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've made a separate paragraph for each for of type aliases, to be specific about what is allow, what is required, and what isn't allowed in each.


A type alias with [_TypeParamBounds_] may only specified when used as
an [associated type] in a [trait].

Where clauses before the equals sign on a type alias in a [trait impl] (like
jackh726 marked this conversation as resolved.
Show resolved Hide resolved
`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
[_TypeParamBounds_]: ../trait-bounds.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