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

RFC: Associated type bounds of form MyTrait<AssociatedType: Bounds> #2289

Merged
merged 9 commits into from
Jul 24, 2018
117 changes: 117 additions & 0 deletions text/0000-associated-type-bounds.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
- Feature Name: associated_type_bounds
- Start Date: 2018-01-13
- RFC PR: (leave this empty)
- Rust Issue: (leave this empty)

# Summary
[summary]: #summary

Introduce the bound form `MyTrait<AssociatedType: Bounds>`, permitted anywhere
a bound of the form `MyTrait<AssociatedType = T>` would be allowed. This form
desugars to `MyTrait<AssociatedType = impl Bounds>`.

# Motivation
[motivation]: #motivation

Currently, when specifying a bound using a trait that has an associated
type, the developer can specify the precise type via the syntax
`MyTrait<AssociatedType = T>`. With the introduction of the `impl Trait`
syntax for static-dispatch existential types, this syntax also permits
`MyTrait<AssociatedType = impl Bounds>`, as a shorthand for introducing a
new type variable and specifying those bounds.

However, this introduces an unnecessary level of indirection that does not
match the developer's intuition and mental model as well as it could. In
particular, given the ability to write bounds on a type variable as `T: Bounds`,
it makes sense to permit writing bounds on an associated type directly.
This results in the simpler syntax `MyTrait<AssociatedType: Bounds>`.

# Guide-level explanation
[guide-level-explanation]: #guide-level-explanation

Instead of specifying a concrete type for an associated type, we can
specify a bound on the associated type, to ensure that it implements
specific traits, as seen in the example below:

```rust
fn print_all<T: Iterator<Item: Display>>(printables: T) {
for p in printables {
println!("{}", p);
}
}
```

## In anonymous existential types

```rust
fn printables() -> impl Iterator<Item: Display> {
// ..
}
```

## Further examples

Instead of writing:

```rust
impl<I> Clone for Peekable<I>
where
I: Clone + Iterator,
<I as Iterator>::Item: Clone,
{
// ..
}
```

you may write:

```rust
impl<I> Clone for Peekable<I>
where
I: Clone + Iterator<Item: Clone>
{
// ..
}
```

or replace the `where` clause entirely:

```rust
impl<I: Clone + Iterator<Item: Clone>> Clone for Peekable<I> {
// ..
}
```

# Reference-level explanation
[reference-level-explanation]: #reference-level-explanation

The surface syntax `Trait<AssociatedType: Bounds>` should desugar to
`Trait<AssociatedType = impl Bounds>` anywhere it appears. This syntax
Copy link
Contributor

@petrochenkov petrochenkov Jan 13, 2018

Choose a reason for hiding this comment

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

I'm not sure literally desugaring like this will be legal and will mean the same thing in all contexts.
Maybe it's better to use the desugaring from the examples above (T: Trait<AssocTy: Bounds> -> T: Trait + <T as Trait>::AssocTy: Bounds) everywhere?

Copy link
Contributor

Choose a reason for hiding this comment

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

Nice sugar, overall.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So in other words: T: Trait<AssocTy: Bounds> desugars into adding <T as Trait>::AssocTy: Bounds to the list of where clauses? How do we deal with that in the case of the following?

fn printables() -> impl Iterator<Item: Display> {
    // ..
}

Copy link
Member

Choose a reason for hiding this comment

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

@Centril -> impl Iterator<Item = T> is already desugared to two bounds, X: Iterator and <X as Iterator>::Item == T, where X is the impl Iterator<Item = T> nominal type.

Copy link
Member

Choose a reason for hiding this comment

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

@petrochenkov That's great! It means we can implement this today with very little effort, since we have all the needed infrastructure from Trait<AssocTy = T> (assuming we don't even need to handle trait objects).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@petrochenkov You are more familiar with the inner workings of the compiler, so I think you can better specify the proper desugaring. Could you PR against our PR perhaps?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Patch in: Centril#2

does not introduce any new semantics that the availability of
`impl Bounds` does not already introduce.

# Drawbacks
[drawbacks]: #drawbacks

With the introduction of the `impl Trait` syntax, Rust code can already
express this using the desugared form. This proposal just introduces a
simpler surface syntax that parallels other uses of bounds. As always,
when introducing new syntactic forms, an increased burden is put on
developers to know about and understand those forms, and this proposal
is no different. However, we believe that the parallel to the use of bounds
elsewhere makes this new syntax immediately recognizable and understandable.

# Rationale and alternatives
[alternatives]: #alternatives

As with any new surface syntax, one alternative is simply not introducing
the syntax at all. That would still leave developers with the
`MyTrait<AssociatedType = impl Bounds>` form. However, allowing the more
direct bounds syntax provides a better parallel to the use of bounds elsewhere.
The introduced form in this RFC is comparatively both shorter and clearer.

# Unresolved questions
[unresolved]: #unresolved-questions

- Does this introduce any parsing ambiguities?
Copy link
Contributor

Choose a reason for hiding this comment

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

No.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We were pretty sure that there wouldn't be any - but it is nice to get that confirmed.
I'll remove this question in a bit then =)

- Does allowing this for `dyn` trait objects introduce any unforseen issues?