Skip to content

Commit 6c22c39

Browse files
Address biggest comments from design meeting
1 parent 1e235ff commit 6c22c39

File tree

1 file changed

+79
-27
lines changed

1 file changed

+79
-27
lines changed

text/3437-implementable-trait-alias.md

Lines changed: 79 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Extend `#![feature(trait_alias)]` to permit `impl` blocks for trait aliases with
99

1010
# Motivation
1111

12-
Often, one desires to have a "weak" version of a trait, as well as a "strong" one providing additional guarantees. Subtrait relationships are commonly used for this, but they sometimes fall short—expecially when the "strong" version is expected to see more use, or was stabilized first.
12+
Often, one desires to have a "weak" version of a trait, as well as a "strong" one providing additional guarantees. Subtrait relationships are commonly used for this, but they sometimes fall short—especially when the "strong" version is expected to see more use, or was stabilized first.
1313

1414
## Example: AFIT `Send` bound aliases
1515

@@ -120,7 +120,7 @@ error[E0404]: expected trait, found trait alias `Frobber`
120120

121121
## Speculative example: GATification of `Iterator`
122122

123-
*This example relies on some language features that are currently pure speculation. Implementable trait aliases are potentially necessary to support this use-case, but not sufficent.*
123+
*This example relies on some language features that are currently pure speculation. Implementable trait aliases are potentially necessary to support this use-case, but not sufficient.*
124124

125125
Ever since the GAT MVP was stabilized, there has been discussion about how to add `LendingIterator` to the standard library, without breaking existing uses of `Iterator`. The relationship between `LendingIterator` and `Iterator` is "weak"/"strong"—an `Iterator` is a `LendingIterator` with some extra guarantees about the `Item` associated type.
126126

@@ -150,7 +150,7 @@ There has been some discussion about a variant of the `Future` trait with an `un
150150

151151
# Guide-level explanation
152152

153-
With `#![feature(trait_alias)]` (RFC #1733), one can define trait aliases, for use in bounds, trait objects, and `impl Trait`. This feature additionaly allows writing `impl` blocks for a subset of trait aliases.
153+
With `#![feature(trait_alias)]` (RFC #1733), one can define trait aliases, for use in bounds, trait objects, and `impl Trait`. This feature additionally allows writing `impl` blocks for a subset of trait aliases.
154154

155155
Let's rewrite our AFIT example from before, in terms of this feature. Here's what it looks like now:
156156

@@ -185,7 +185,7 @@ impl Frobber for MyType {
185185

186186
Joe's original code Just Works.
187187

188-
The rule of thumb is: if you can copy everything between the `=` and `;` of a trait alias, paste it between the `for` and `{` of a trait `impl` block, and the result is sytactically valid—then the trait alias is most likely implementable.
188+
The rule of thumb is: if you can copy everything between the `=` and `;` of a trait alias, paste it between the `for` and `{` of a trait `impl` block, and the result is syntactically valid—then the trait alias is most likely implementable.
189189

190190
# Reference-level explanation
191191

@@ -203,22 +203,9 @@ Implementable trait aliases must follow a more restrictive form:
203203
204204
For example, `trait Foo<T> = PartialEq<T> where Self: Sync;` is a valid implementable alias. The `=` must be followed by a single trait (or implementable trait alias), and then some number of where clauses. The trait's generic parameter list may contain associated type constraints (for example `trait IntIterator = Iterator<Item = u32>`).
205205

206-
There is another restriction that trait aliases must adhere to in order to be implementable: all generic parameters of the alias itself must be used as generic parameters of the alias's primary trait.
207-
208-
```rust
209-
// Implementable
210-
trait Foo<T> = PartialEq<T>;
211-
212-
// Not implementable
213-
trait Foo<T> = Copy;
214-
trait Foo<T> = Copy where T: Send;
215-
trait Foo<T> = Iterator<Item = T>;
216-
trait Foo<T> = Copy where Self: PartialEq<T>;
217-
```
218-
219206
## Usage in `impl` blocks
220207

221-
An impl block for a trait alias looks just like an impl block for the underlying trait. The alias's where clauses are treated as if they had been written out in the `impl` header.
208+
An `impl` block for a trait alias looks just like an `impl` block for the underlying trait. The alias's where clauses are enforced as requirements that the `impl`ing type must meet—just like `where` clauses in trait declarations are treated.
222209

223210
```rust
224211
pub trait CopyIterator = Iterator<Item: Copy> where Self: Send;
@@ -236,8 +223,19 @@ impl CopyIterator for Foo {
236223
struct Bar;
237224
impl !Send for Bar;
238225

239-
// ERROR: `Bar` is not `Send`
240-
// impl IntIterator for Bar { /* ... */ }
226+
//impl CopyIterator for Bar { /* ... */ } // ERROR: `Bar` is not `Send`
227+
```
228+
229+
```rust
230+
trait Foo {}
231+
trait Bar = Foo where Self: Send;
232+
//impl<T> Bar for T {} // ERROR: Need to add `T: Send` bound
233+
```
234+
```rust
235+
#![feature(trivial_bounds)]
236+
trait Foo {}
237+
trait Bar = Foo where String: Copy;
238+
//impl Bar for () {} // ERROR: `String: Copy` not satisfied
241239
```
242240

243241
Bounds on generic parameters are also enforced at the `impl` site.
@@ -268,6 +266,28 @@ impl IntIterator for Baz {
268266
}
269267
```
270268

269+
Such constraints can be inferred indirectly:
270+
271+
```rust
272+
trait Bar: Iterator<Item = i32> {}
273+
pub trait IntIterator = Iterator where Self: Bar;
274+
275+
struct Baz;
276+
277+
impl Bar for Baz {}
278+
279+
impl IntIterator for Baz {
280+
// `IntIterator` requires `Bar`,
281+
// which requires `Iterator<Item = i32>`,
282+
// so `Item` must be `i32`
283+
// and we don't need to specify it.
284+
285+
fn next(&mut self) -> i32 {
286+
-27
287+
}
288+
}
289+
```
290+
271291
Alias `impl`s also allow omitting implied `#[refine]`s:
272292

273293
```rust
@@ -306,11 +326,13 @@ Trait aliases are `unsafe` to implement iff the underlying trait is marked `unsa
306326
Implementable trait aliases can also be used with trait-qualified and fully-qualified method call syntax, as well as in paths more generally. When used this way, they are treated equivalently to the underlying primary trait, with the additional restriction that all `where` clauses and type parameter/associated type bounds must be satisfied.
307327

308328
```rust
329+
use std::array;
330+
309331
trait IntIter = Iterator<Item = u32> where Self: Clone;
310332

311333
let iter = [1_u32].into_iter();
312334
let _: IntIter::Item = IntIter::next(&mut iter); // works
313-
let _: <std::array::IntoIter as IntIter>::Item = <std::array::IntoIter as IntIter>::next(); // works
335+
let _: <array::IntoIter as IntIter>::Item = <array::IntoIter as IntIter>::next(); // works
314336
//IntIter::clone(&iter); // ERROR: trait `Iterator` has no method named `clone()`
315337
let dyn_iter: &mut dyn Iterator<Item = u32> = &mut iter;
316338
//IntIter::next(dyn_iter); // ERROR: `dyn Iterator<Item = u32>` does not implement `Clone`
@@ -331,9 +353,7 @@ let _: IntIter<Item = u32> = [1_u32].into_iter(); // `Item = u32` is redundant,
331353

332354
# Drawbacks
333355

334-
- The sytactic distance between implementable and non-implementable aliases is short, which might confuse users. In particular, the fact that `trait Foo = Bar + Send;` means something different than `trait Foo = Bar where Self: Send;` will likely be surprising to many.
335-
- On the other hand, the rules mirror those of `impl` blocks, which Rust programmers already understand.
336-
- Ideally, we would collect user feedback before stabilizing this feature.
356+
- The syntactic distance between implementable and non-implementable aliases is short, which might confuse users. In particular, the fact that `trait Foo = Bar + Send;` means something different than `trait Foo = Bar where Self: Send;` will likely be surprising to many.
337357
- Adds complexity to the language, which might surprise or confuse users.
338358
- Many of the motivating use-cases involve language features that are not yet stable, or even merely speculative. More experience with those features might unearth better alternatives.
339359

@@ -343,12 +363,43 @@ let _: IntIter<Item = u32> = [1_u32].into_iter(); // `Item = u32` is redundant,
343363
- Better ergonomics compared to purely proc-macro based solutions.
344364
- One alternative is to allow marker traits or auto traits to appear in `+` bounds of implementable aliases.
345365
(For example, `trait Foo = Bar + Send;` could be made implementable).
346-
- This may make the implementablility rules more intutive to some, as the distinction between `+ Send` and `where Self: Send` would no longer be present.
366+
- This may make the implementablility rules more intuitive to some, as the distinction between `+ Send` and `where Self: Send` would no longer be present.
347367
- However, it also might make the rules less intuitive, as the symmetry with `impl` blocks would be broken.
368+
- Also, such a change might break the commutativity of `+`, or make it less obvious which trait is being implemented.
348369
- Again, user feedback could help make this decision.
349370
- Another option is to require an attribute on implementable aliases; e.g. `#[implementable] trait Foo = ...`. This would make the otherwise-subtle implementability rules more explicit, at the cost of cluttering user code and the attribute namespace.
371+
- A previous version of this RFC required type parameters of implementable trait aliases to be used as type parameters of the alias's primary trait. This restriction was meant to avoid surprising errors:
372+
373+
```rust
374+
trait Foo<T> = Copy;
375+
376+
#[derive(Clone)]
377+
struct MyType;
378+
379+
impl<T> Foo<T> for MyType {} // ERROR: `T`` is unconstrained
380+
```
381+
382+
```rust
383+
trait Foo<T> = Iterator<Item = T>;
384+
385+
struct MyType;
386+
387+
impl Foo<u32> for MyType {
388+
fn next(&mut Self) -> Option<u32> {
389+
todo!()
390+
}
391+
}
392+
393+
impl Foo<i32> for MyType { // ERROR: overlapping impls
394+
fn next(&mut Self) -> Option<i32> {
395+
todo!()
396+
}
397+
}
398+
```
399+
400+
However, upon further discussion, I now lean toward allowing more flexibility, even at the risk of potential confusion.
350401

351-
## What about combining multiple primary traits, and their items, into one impl block?
402+
## What about combining multiple primary traits, and their items, into one `impl` block?
352403

353404
It's possible to imagine an extension of this proposal, that allows trait aliases to be implementable even if they have multiple primary traits. For example:
354405

@@ -385,7 +436,7 @@ trait B {
385436
trait C = A + B;
386437
```
387438

388-
Such a feature could also make it harder to find the declaration of a trait item from its implementation, especially if IDE "go to definition" is not available. One would need to first find the trait alias definition, and then look through every primary trait to find the item. (However, given the current situation with postfix method call syntax, maybe this is an acceptable tradeoff.)
439+
Such a feature could also make it harder to find the declaration of a trait item from its implementation, especially if IDE "go to definition" is not available. One would need to first find the trait alias definition, and then look through every primary trait to find the item. (However, given the current situation with postfix method call syntax, maybe this is an acceptable trade-off.)
389440

390441
Perhaps a more narrowly tailored version of this extension, in which both subtrait and supertrait explicitly opt-in to support sharing an `impl` block with one another, would satisfy the backward-compatibility use-case while avoiding the above issues. I think exploring that is best left to a future RFC.
391442

@@ -403,4 +454,5 @@ Perhaps a more narrowly tailored version of this extension, in which both subtra
403454
- Variance bounds would allow this feature to support backward-compatible GATification.
404455
- Method unsafety bounds would support the `Future``Async` use-case.
405456
- `trait Foo: Copy = Iterator;` could be allowed as an alternative to `trait Foo = Iterator where Self: Copy;`.
457+
- `impl Trait<Assoc = Ty> for Type { /* ... */ }` could be permitted in the future, to make the "copy-paste" rule of thumb work better.
406458
- The possible contents of `impl` bodies could be expanded, for example to support combining supertrait and subtrait implementations.

0 commit comments

Comments
 (0)