diff --git a/text/2289-associated-type-bounds.md b/text/2289-associated-type-bounds.md new file mode 100644 index 00000000000..92a021137cb --- /dev/null +++ b/text/2289-associated-type-bounds.md @@ -0,0 +1,243 @@ +- Feature Name: `associated_type_bounds` +- Start Date: 2018-01-13 +- RFC PR: [rust-lang/rfcs#2289](https://github.com/rust-lang/rfcs/pull/2289) +- Rust Issue: [rust-lang/rust#52662](https://github.com/rust-lang/rust/issues/52662) + +# Summary +[summary]: #summary + +Introduce the bound form `MyTrait`, permitted anywhere +a bound of the form `MyTrait` would be allowed. The bound +`T: Trait` desugars to the bounds `T: Trait` and +`::AssociatedType: Bounds`. +See the [reference][reference-level-explanation] and [rationale][alternatives] +for exact details. + +# 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`. With the introduction of the `impl Trait` +syntax for static-dispatch existential types, this syntax also permits +`MyTrait`, 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`. + +# 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>(printables: T) { + for p in printables { + println!("{}", p); + } +} +``` + +## In anonymous existential types + +```rust +fn printables() -> impl Iterator { + // .. +} +``` + +## Further examples + +Instead of writing: + +```rust +impl Clone for Peekable +where + I: Clone + Iterator, + ::Item: Clone, +{ + // .. +} +``` + +you may write: + +```rust +impl Clone for Peekable +where + I: Clone + Iterator +{ + // .. +} +``` + +or replace the `where` clause entirely: + +```rust +impl> Clone for Peekable { + // .. +} +``` + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +The surface syntax `T: Trait` should desugar to a pair +of bounds: `T: Trait` and `::AssociatedType: Bounds`. +Rust currently allows both of those bounds anywhere a bound can currently appear; +the new syntax does not introduce any new semantics. + +Additionally, the surface syntax `impl Trait` turns +into a named type variable `T`, universal or existential depending on context, +with the usual bound `T: Trait` along with the added bound +`::AssociatedType: Bounds`. + +Meanwhile, the surface syntax `dyn Trait` desugars into +`dyn Trait` where `T` is a named type variable `T` with the +bound `T: Bounds`. + +## The desugaring for associated types + +In the case of an associated type having a bound of the form: + +```rust +trait TraitA { + type AssocA: TraitB; +} +``` + +we desugar to an anonymous associated type for `AssocB`, which corresponds to: + +```rust +trait TraitA { + type AssocA: TraitB; + type AssocA_0: TraitC; // Associated type is Unnamed! +} +``` + +## Notes on the meaning of `impl Trait` + +Note that in the context `-> impl Trait`, since the Trait is +existentially quantified, the `Assoc` is as well. Semantically speaking, +`fn printables..` is equivalent to: + +```rust +fn printables() -> impl Iterator { .. } +``` + +For `arg: impl Trait`, it is semantically equivalent to: +`arg: impl Trait`. + +## Meaning of `existential type Foo: Trait` + +Given: + +``` +existential type Foo: Trait; +``` + +it can be seen as the same as: + +```rust +existential type Foo: Trait; +existential type _0: Bound; +``` + +[RFC 2071]: https://github.com/rust-lang/rfcs/blob/master/text/2071-impl-trait-type-alias.md + +This syntax is specified in [RFC 2071]. As in that RFC, this documentation +uses the non-final syntax for existential type aliases. + +# Drawbacks +[drawbacks]: #drawbacks + +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]: #rationale-and-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` 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. + +### An alternative desugaring of bounds on associated types + +[RFC 2089]: https://github.com/rust-lang/rfcs/blob/master/text/2089-implied-bounds.md + +An alternative desugaring of the following definition: + +```rust +trait TraitA { + type AssocA: TraitB; +} +``` + +is to add the `where` clause, as specified above, to the trait, desugaring to: + +```rust +trait TraitA +where + ::AssocB: TraitC, +{ + type AssocA: TraitB; +} +``` + +However, at the time of this writing, a Rust compiler will treat this +differently than the desugaring proposed in the reference. +The following snippet illustrates the difference: + +```rust +trait Foo where ::Item: Copy { + type Bar: Iterator; +} + +trait Foo2 { + type Bar: Iterator; + type BarItem: Copy; +} + +fn use_foo(arg: X) +where ::Item: Copy +// ^-- Remove this line and it will error with: +// error[E0277]: `<::Bar as std::iter::Iterator>::Item` doesn't implement `Copy` +{ + let item: ::Item; +} + +fn use_foo2(arg: X) { + let item: ::Item; +} +``` + +The desugaring with a `where` therefore becomes problematic from a perspective +of usability. + +However, [RFC 2089, Implied Bounds][RFC 2089] specifies that desugaring to the +`where` clause in the trait will permit the `use_foo` function to omit its +`where` clause. This entails that both desugarings become equivalent from the +point of view of a user. The desugaring with `where` therefore becomes viable +in the presence of [RFC 2089]. + +# Unresolved questions +[unresolved]: #unresolved-questions + +- Does allowing this for `dyn` trait objects introduce any unforeseen issues? + This can be resolved during stabilization. + +- The exact desugaring in the context of putting bounds on an associated type + of a trait is left unresolved. The semantics should however be preserved. + This is also the case with other desugarings in this RFC.