diff --git a/text/0000-trait-alias.md b/text/0000-trait-alias.md new file mode 100644 index 00000000000..c8ebef4131d --- /dev/null +++ b/text/0000-trait-alias.md @@ -0,0 +1,440 @@ +- Feature Name: Trait alias +- Start Date: 2016-08-31 +- RFC PR: +- Rust Issue: + +# Summary +[summary]: #summary + +Traits can be aliased with the `trait TraitAlias = …;` construct. Currently, the right hand side is +a bound – a single trait, a combination with `+` traits and lifetimes. Type parameters and +lifetimes can be added to the *trait alias* if needed. + +# Motivation +[motivation]: #motivation + +## First motivation: `impl` + +Sometimes, some traits are defined with parameters. For instance: + +```rust +pub trait Foo { + // ... +} +``` + +It’s not uncommon to do that in *generic* crates and implement them in *backend* crates, where the +`T` template parameter gets substituted with a *backend* type. + +```rust +// in the backend crate +pub struct Backend; + +impl Foo for i32 { + // ... +} +``` + +Users who want to use that crate will have to export both the trait `Foo` from the generic crate +*and* the backend singleton type from the backend crate. Instead, we would like to be able to leave +the backend singleton type hidden in the crate. The first shot would be to create a new trait for +our backend: + +```rust +pub trait FooBackend: Foo { + // ... +} + +fn use_foo(_: A) where A: FooBackend {} +``` + +If you try to pass an object that implements `Foo`, that won’t work, because it doesn’t +implement `FooBackend`. However, we can make it work with the following universal `impl`: + +```rust +impl FooBackend for T where T: Foo {} +``` + +With that, it’s now possible to pass an object that implements `Foo` to a function +expecting a `FooBackend`. However, what about impl blocks? What happens if we implement only +`FooBackend`? Well, we cannot, because the trait explicitely states that we need to implement +`Foo`. We hit a problem here. The problem is that even though there’s a compatibility at +the `trait bound` level between `Foo` and `FooBackend`, there’s none at the `impl` level, +so all we’re left with is implementing `Foo` – that will also provide an implementation for +`FooBackend` because of the universal implementation just above. + +## Second example: ergonomic collections and scrapping boilerplate + +Another example is associated types. Take the following [trait from tokio](https://docs.rs/tokio-service/0.1.0/tokio_service/trait.Service.html): + +```rust +pub trait Service { + type Request; + type Response; + type Error; + type Future: Future; + fn call(&self, req: Self::Request) -> Self::Future; +} +``` + +It would be nice to be able to create a few aliases to remove boilerplate for very common +combinations of associated types with `Service`. + +```rust +Service; +``` + +The trait above is a http service trait which only the associated type `Future` is left to be +implemented. Such an alias would be very appealing because it would remove copying the whole +`Service` trait into use sites – trait bounds, or even trait impls. Scrapping such an annoying +boilerplate is a definitive plus to the language and might be one of the most interesting use case. + +# Detailed design +[design]: #detailed-design + +## Syntax + +### Declaration + +The syntax chosen to declare a *trait alias* is: + +```rust +trait TraitAlias = Trait; +``` + +Trait aliasing to combinations of traits is also provided with the standard `+` construct: + +```rust +trait DebugDefault = Debug + Default; +``` + +Optionally, if needed, one can provide a `where` clause to express *bounds*: + +```rust +trait DebugDefault = Debug where Self: Default; // same as the example above +``` + +Furthermore, it’s possible to use only the `where` clause by leaving the list of traits empty: + +```rust +trait DebugDefault = where Self: Debug + Default; +``` + +It’s also possible to partially bind associated types of the right hand side: + +```rust +trait IntoIntIterator = IntoIterator; +``` + +This would leave `IntoIntIterator` with a *free parameter* being `IntoIter`, and it should be bind +the same way associated types are bound with regular traits: + +```rust +fn foo(int_iter: I) where I: IntoIntIterator> {} +``` + +A trait alias can be parameterized over types and lifetimes, just like traits themselves: + +```rust +trait LifetimeParametric<'a> = Iterator>;` + +trait TypeParametric = Iterator>; +``` + +--- + +Specifically, the grammar being added is, in informal notation: + +``` +ATTRIBUTE* VISIBILITY? trait IDENTIFIER()? = GENERIC_BOUNDS (where PREDICATES)?; +``` + +`GENERIC_BOUNDS` is a list of zero or more traits and lifetimes separated by `+`, the same as the +current syntax for bounds on a type parameter, and `PREDICATES` is a comma-separated list of zero or +more predicates, just like any other `where` clause. +`GENERIC_PARAMS` is a comma-separated list of zero or more lifetime and type parameters, +with optional bounds, just like other generic definitions. + +## Use semantics + +You cannot directly `impl` a trait alias, but you can have them as *bounds*, *trait objects* and +*impl Trait*. + +---- + +It is an error to attempt to override a previously specified +equivalence constraint with a non-equivalent type. For example: + +```rust +trait SharableIterator = Iterator + Sync; +trait IntIterator = Iterator; + +fn quux1>(...) { ... } // ok +fn quux2>(...) { ... } // ok (perhaps subject to lint warning) +fn quux3>(...) { ... } // ERROR: `Item` already constrained + +trait FloIterator = IntIterator; // ERROR: `Item` already constrained +``` + +--- + +When using a trait alias as a trait object, it is subject to object safety restrictions *after* +substituting the aliased traits. This means: + +1. it contains an object safe trait, optionally a lifetime, and zero or more of these other bounds: + `Send`, `Sync` (that is, `trait Show = Display + Debug;` would not be object safe); +2. all the associated types of the trait need to be specified; +3. the `where` clause, if present, only contains bounds on `Self`. + +Some examples: + +```rust +trait Sink = Sync; +trait ShareableIterator = Iterator + Sync; +trait PrintableIterator = Iterator + Display; +trait IntIterator = Iterator; + +fn foo1(...) { ... } // ok +fn foo2>(...) { ... } // ok +fn bar1(x: Box) { ... } // ERROR: associated type not specified +fn bar2(x: Box>) { ... } // ok +fn bar3(x: Box) { ... } // ERROR: too many traits (*) +fn bar4(x: Box) { ... } // ok (*) +``` + +The lines marked with `(*)` assume that [#24010](https://github.com/rust-lang/rust/issues/24010) is +fixed. + +### Ambiguous constraints + +If there are multiple associated types with the same name in a trait alias, +then it is a static error ("ambiguous associated type") to attempt to +constrain that associated type via the trait alias. For example: + +```rust +trait Foo { type Assoc; } +trait Bar { type Assoc; } // same name! + +// This works: +trait FooBar1 = Foo + Bar; + +// This does not work: +trait FooBar2 = Foo + Bar; +fn badness>() { } // ERROR: ambiguous associated type + +// Here are ways to workaround the above error: +fn better1>() { } // (leaves Bar::Assoc unconstrained) +fn better2 + Bar>() { } // constrains both +``` + +# Teaching +[teaching]: #teaching + +[Traits](https://doc.rust-lang.org/book/traits.html) are obviously a huge prerequisite. Traits +aliases could be introduced at the end of that chapter. + +Conceptually, a *trait alias* is a syntax shortcut used to reason about one or more trait(s). +Inherently, the *trait alias* is usable in a limited set of places: + +- as a *bound*: exactly like a *trait*, a *trait alias* can be used to constraint a type (type + parameters list, where-clause) +- as a *trait object*: same thing as with a *trait*, a *trait alias* can be used as a *trait object* + if it fits object safety restrictions (see above in the [semantics](#semantics) section) +- in an [`impl Trait`](https://github.com/rust-lang/rfcs/blob/master/text/1522-conservative-impl-trait.md) + +Examples should be showed for all of the three cases above: + +#### As a bound + +```rust +trait StringIterator = Iterator; + +fn iterate(si: SI) where SI: StringIterator {} // used as bound +``` + +#### As a trait object + +```rust +fn iterate_object(si: &StringIterator) {} // used as trait object +``` + +#### In an `impl Trait` + +```rust +fn string_iterator_debug() -> impl Debug + StringIterator {} // used in an impl Trait +``` + +As shown above, a *trait alias* can substitute associated types. It doesn’t have to substitute them +all. In that case, the *trait alias* is left incomplete and you have to pass it the associated types +that are left. Example with the [tokio case](#second-example-ergonomic-collections-and-scrapping-boilerplate): + +```rust +pub trait Service { + type Request; + type Response; + type Error; + type Future: Future; + fn call(&self, req: Self::Request) -> Self::Future; +} + +trait HttpService = Service; + +trait MyHttpService = HttpService; // assume MyFuture exists and fulfills the rules to be used in here +``` + +# Drawbacks +[drawbacks]: #drawbacks + +- Adds another construct to the language. + +- The syntax `trait TraitAlias = Trait` requires lookahead in the parser to disambiguate a trait + from a trait alias. + +# Alternatives +[alternatives]: #alternatives + +## Should we use `type` as the keyword instead of `trait`? + +`type Foo = Bar;` already creates an alias `Foo` that can be used as a trait object. + +If we used `type` for the keyword, this would imply that `Foo` could also be used as a bound as +well. If we use `trait` as proposed in the body of the RFC, then `type Foo = Bar;` and +`trait Foo = Bar;` _both_ create an alias for the object type, but only the latter creates an alias +that can be used as a bound, which is a confusing bit of redundancy. + +However, this mixes the concepts of types and traits, which are different, and allows nonsense like +`type Foo = Rc + f32;` to parse. + +## Supertraits & universal `impl` + +It’s possible to create a new trait that derives the trait to alias, and provide a universal `impl` + +```rust +trait Foo {} + +trait FooFakeAlias: Foo {} + +impl Foo for T where T: FooFakeAlias {} +``` + +This works for trait objects and trait bounds only. You cannot implement `FooFakeAlias` directly +because you need to implement `Foo` first – hence, you don’t really need `FooFakeAlias` if you can +implement `Foo`. + +There’s currently no alternative to the impl problem described here. + +## `ConstraintKinds` + +Similar to GHC’s `ContraintKinds`, we could declare an entire predicate as a reified list of +constraints, instead of creating an alias for a set of supertraits and predicates. Syntax would be +something like `constraint Foo = T: Bar, Vec: Baz;`, used as `fn quux(...) where Foo { ... }` +(i.e. direct substitution). Trait object usage is unclear. + +## Syntax for sole `where` clause. + +The current RFC specifies that it is possible to use only the `where` clause by leaving the list of traits empty: + +```rust +trait DebugDefault = where Self: Debug + Default; +``` + +This is one of many syntaxes that are available for this construct. Alternatives include: + + * `trait DebugDefault where Self: Debug + Default;` (which has been [considered and discarded](https://github.com/rust-lang/rfcs/pull/1733#issuecomment-257993316) because [it might look](https://github.com/rust-lang/rfcs/pull/1733#issuecomment-258495468) too much like a new trait definition) + * `trait DebugDefault = _ where Self: Debug + Default;` (which was [considered and then removed](https://github.com/rust-lang/rfcs/pull/1733/commits/88d3074957276c7201147fc625f18e0ebcecc1b9#diff-ae27a1a8d977f731e67823349151bed5L116) because it is [technically unnecessary](https://github.com/rust-lang/rfcs/pull/1733#issuecomment-284252196)) + * `trait DebugDefault = Self where Self: Debug + Default;` (analogous to previous case but not formally discussed) + +# Unresolved questions +[unresolved]: #unresolved-questions + +## Trait alias containing only lifetimes + +This is annoying. Consider: + +```rust +trait Static = 'static; + +fn foo(t: T) where T: Static {} +``` + +Such an alias is legit. However, I feel concerned about the actual meaning of the declaration – i.e. +using the `trait` keyword to define alias on *lifetimes* seems a wrong design choice and seems not +very consistent. + +If we chose another keyword, like `constraint`, I feel less concerned and it would open further +opportunies – see the `ConstraintKinds` alternative discussion above. + +## Which bounds need to be repeated when using a trait alias? + +[RFC 1927](https://github.com/rust-lang/rfcs/pull/1927) intends to change the rules here for traits, +and we likely want to have the rules for trait aliases be the same to avoid confusion. + +The `constraint` alternative sidesteps this issue. + +## What about bounds on type variable declaration in the trait alias? + +```rust +trait Foo = PartialEq; +``` + +`PartialEq` has no super-trait `Bar`, but we’re adding one via our trait alias. What is the behavior +of such a feature? One possible desugaring is: + +```rust +trait Foo = where Self: PartialEq, T: Bar; +``` + +[Issue 21903](https://github.com/rust-lang/rust/issues/21903) explains the same problem for type +aliasing. + +**Note: what about the following proposal below?** + +When using a trait alias as a bound, you cannot add extra bound on the input parameters, like in the +following: + +```rust +trait Foo = PartialEq; +``` + +Here, `T` adds a `Bar` bound. Now consider: + +```rust +trait Bar = PartialEq; +``` + +Currently, we don’t have a proper understanding of that situation, because we’re adding in both +cases a bound, and we don’t know how to disambiguate between *pre-condition* and *implication*. That +is, is that added `Bar` bound a constraint that `T` must fulfil in order for the trait alias to be +met, or is it a constraint the trait alias itself adds? To disambiguate, consider: + +```rust +trait BarPrecond where T: Bar = PartialEq; +trait BarImplic = PartialEq where T: Bar; +trait BarImpossible where T: Bar = PartialEq where T: Bar; +``` + +`BarPrecond` would require the use-site code to fulfil the constraint, like the following: + +```rust +fn foo() where A: BarPrecond, T: Bar {} +``` + +`BarImplic` would give us `T: Bar`: + +```rust +fn foo() where A: BarImplic { + // T: Bar because given by BarImplic +} +``` + +`BarImpossible` wouldn’t compile because we try to express a pre-condition and an implication for +the same bound at the same time. However, it’d be possible to have both a pre-condition and an +implication on a parameter: + +```rust +trait BarBoth where T: Bar = PartialEq where T: Debug; + +fn foo() where A: BarBoth, T: Bar { + // T: Debug because given by BarBoth +} +```