Skip to content

Commit f03d3b9

Browse files
Expand alternatives, fix implementability rules hole
1 parent 1b32952 commit f03d3b9

File tree

1 file changed

+46
-36
lines changed

1 file changed

+46
-36
lines changed

text/3437-implementable-trait-alias.md

Lines changed: 46 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +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.
13-
Subtrait relationships are commonly used for this, but they sometimes fall short—expecially when the "strong" version is
14-
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—expecially when the "strong" version is expected to see more use, or was stabilized first.
1513

1614
## Example: AFIT `Send` bound aliases
1715

@@ -26,9 +24,7 @@ pub trait Frobber {
2624
}
2725
```
2826

29-
Most of `frob-lib`'s users will need `Frobber::frob`'s return type to be `Send`,
30-
so the library wants to make this common case as painless as possible. But non-`Send`
31-
usage should be supported as well.
27+
Most of `frob-lib`'s users will need `Frobber::frob`'s return type to be `Send`, so the library wants to make this common case as painless as possible. But non-`Send` usage should be supported as well.
3228

3329
`frob-lib`, following the recommended practice, decides to design its API in the following way:
3430

@@ -95,8 +91,7 @@ impl LocalFrobber for MyType {
9591
}
9692
```
9793

98-
This is distinctly worse. Joe now has to reference both `Frobber` and `LocalFrobber` in his code,
99-
and (assuming that the final AFIT feature ends up requiring it) also has to write `#[refine]`.
94+
This is distinctly worse. Joe now has to reference both `Frobber` and `LocalFrobber` in his code, and (assuming that the final AFIT feature ends up requiring it) also has to write `#[refine]`.
10095

10196
### With today's `#![feature(trait_alias)]`
10297

@@ -113,8 +108,7 @@ pub trait LocalFrobber {
113108
pub trait Frobber = LocalFrobber<frob(..): Send> + Send;
114109
```
115110

116-
With today's `trait_alias`, it wouldn't make much difference for Joe. He would just get a slightly different
117-
error message:
111+
With today's `trait_alias`, it wouldn't make much difference for Joe. He would just get a slightly different error message:
118112

119113
```
120114
error[E0404]: expected trait, found trait alias `Frobber`
@@ -126,15 +120,11 @@ error[E0404]: expected trait, found trait alias `Frobber`
126120

127121
## Speculative example: GATification of `Iterator`
128122

129-
*This example relies on some language features that are currently pure speculation.
130-
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 sufficent.*
131124

132-
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`.
133-
The relationship between `LendingIterator` and `Iterator` is "weak"/"strong"—an `Iterator` is a `LendingIterator` with some extra guarantees about the `Item` associated type.
125+
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.
134126

135-
Now, let's imagine that Rust had some form of "variance bounds",
136-
that allowed restricting the way in which a type's GAT can depend on said GAT's generic parameters.
137-
One could then define `Iterator` in terms of `LendingIterator`, like so:
127+
Now, let's imagine that Rust had some form of "variance bounds", that allowed restricting the way in which a type's GAT can depend on said GAT's generic parameters. One could then define `Iterator` in terms of `LendingIterator`, like so:
138128

139129
```rust
140130
//! core::iter
@@ -152,8 +142,7 @@ where
152142
for<'a> Self::Item<'a>: bivariant_in<'a>;
153143
```
154144

155-
But, as with the previous example, we are foiled by the fact that trait aliases aren't `impl`ementable,
156-
so this change would break every `impl Iterator` block in existence.
145+
But, as with the previous example, we are foiled by the fact that trait aliases aren't `impl`ementable, so this change would break every `impl Iterator` block in existence.
157146

158147
## Speculative example: `Async` trait
159148

@@ -196,8 +185,7 @@ impl Frobber for MyType {
196185

197186
Joe's original code Just Works.
198187

199-
The rule of thumb is: if you can copy everything between the `=` and `;` of a trait alias, paste it between the
200-
`for` and `{` of a trait `impl` block, and the result is sytactically valid—then the trait alias is 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 sytactically valid—then the trait alias is most likely implementable.
201189

202190
# Reference-level explanation
203191

@@ -213,8 +201,7 @@ Implementable trait aliases must follow a more restrictive form:
213201
214202
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.
215203

216-
An impl block for a trait alias looks just like an impl block for the underlying trait. The alias's where clauses
217-
are treated as obligations that the the `impl`ing type must meet.
204+
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.
218205

219206
```rust
220207
pub trait CopyIterator = Iterator<Item: Copy> where Self: Send;
@@ -236,6 +223,29 @@ impl !Send for Bar;
236223
// impl IntIterator for Bar { /* ... */ }
237224
```
238225

226+
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.
227+
228+
```rust
229+
// Implementable
230+
trait Foo<T> = PartialEq<T>;
231+
232+
// Not implementable
233+
trait Foo<T> = Copy;
234+
trait Foo<T> = Copy where T: Send;
235+
trait Foo<T> = Iterator<Item = T>;
236+
trait Foo<T> = Copy where Self: PartialEq<T>;
237+
```
238+
239+
Bounds on such generic parameters are enforced at the `impl` site.
240+
241+
```rust
242+
trait Underlying<T> {}
243+
244+
trait Alias<T: Send> = Underlying<T>;
245+
246+
impl<T> Alias<T> for i32 {} // Error: missing `T: Send` bound
247+
```
248+
239249
If the trait alias uniquely constrains a portion of the `impl` block, that part can be omitted.
240250

241251
```rust
@@ -287,8 +297,7 @@ impl Frobber for MyType {
287297

288298
Trait aliases are `unsafe` to implement iff the underlying trait is marked `unsafe`.
289299

290-
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 associated type bounds
291-
must be satisfied.
300+
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.
292301

293302
```rust
294303
trait IntIter = Iterator<Item = u32> where Self: Clone;
@@ -307,23 +316,24 @@ fn foo() {
307316

308317
# Drawbacks
309318

310-
- The sytactic distance between implementable and non-implementable aliases is short, which might confuse users.
311-
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.
319+
- 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.
312320
- On the other hand, the rules mirror those of `impl` blocks, which Rust programmers already understand.
313321
- Ideally, we would collect user feedback before stabilizing this feature.
314-
- Adds complexity to the language.
315-
- Many of the motivating use-cases involve language features that are not yet stable, or even merely speculative.
316-
More experience with those features might unearth better alternatives.
322+
- Adds complexity to the language, which might surprise or confuse users.
323+
- 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.
317324

318325
# Rationale and alternatives
319326

320327
- Very lightweight, with no new syntax forms. Compare "trait transformers" proposals, for example—they are generally much heavier.
321328
- Better ergonomics compared to purely proc-macro based solutions.
322329
- One alternative is to allow marker traits or auto traits to appear in `+` bounds of implementable aliases.
323-
(For example, `trait Foo = Bar + Send;` could be made implementable). However, this would arguably make implementability rules less intuitive, as the symmetry with `impl` blocks would be broken.
324-
- Another possibility is to require an attribute on implementable aliases; e.g. `#[implementable] trait Foo = ...`. This would make the otherwise-subtle implementability rules explicit, at the cost of cluttering the attribute namespace.
330+
(For example, `trait Foo = Bar + Send;` could be made implementable).
331+
- This may make the implementablility rules more intutive to some, as the distinction between `+ Send` and `where Self: Send` would no longer be present.
332+
- However, it also might make the rules less intuitive, as the symmetry with `impl` blocks would be broken.
333+
- Again, user feedback could help make this decision.
334+
- 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.
325335

326-
## What about combining multiple prtimary traits, and their items, into one impl block?
336+
## What about combining multiple primary traits, and their items, into one impl block?
327337

328338
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:
329339

@@ -370,12 +380,12 @@ Perhaps a more narrowly tailored version of this extension, in which both subtra
370380

371381
# Unresolved questions
372382

373-
- How does `rustdoc` render these? Consider the `Frobber` example—ideally, `Frobber` should be emphasized
374-
compared to `LocalFrobber`, but it's not clear how that would work.
383+
- How does `rustdoc` render these? Consider the `Frobber` example—ideally, `Frobber` should be emphasized compared to `LocalFrobber`, but it's not clear how that would work.
375384

376385
# Future possibilities
377386

378387
- New kinds of bounds: anything that makes `where` clauses more powerful would make this feature more powerful as well.
379388
- Variance bounds would allow this feature to support backward-compatible GATification.
380389
- Method unsafety bounds would support the `Future``Async` use-case.
381-
- Allow `trait Foo: Copy = Iterator;` as alternative to `trait Foo = Iterator where Self: Copy;`.
390+
- `trait Foo: Copy = Iterator;` could be allowed as an alternative to `trait Foo = Iterator where Self: Copy;`.
391+
- The possible contents of `impl` bodies could be expanded, for example to support combining supertrait and subtrait implementations.

0 commit comments

Comments
 (0)