You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: text/3437-implementable-trait-alias.md
+46-36Lines changed: 46 additions & 36 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -9,9 +9,7 @@ Extend `#![feature(trait_alias)]` to permit `impl` blocks for trait aliases with
9
9
10
10
# Motivation
11
11
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.
15
13
16
14
## Example: AFIT `Send` bound aliases
17
15
@@ -26,9 +24,7 @@ pub trait Frobber {
26
24
}
27
25
```
28
26
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.
32
28
33
29
`frob-lib`, following the recommended practice, decides to design its API in the following way:
34
30
@@ -95,8 +91,7 @@ impl LocalFrobber for MyType {
95
91
}
96
92
```
97
93
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]`.
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:
118
112
119
113
```
120
114
error[E0404]: expected trait, found trait alias `Frobber`
@@ -126,15 +120,11 @@ error[E0404]: expected trait, found trait alias `Frobber`
126
120
127
121
## Speculative example: GATification of `Iterator`
128
122
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.*
131
124
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.
134
126
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:
138
128
139
129
```rust
140
130
//! core::iter
@@ -152,8 +142,7 @@ where
152
142
for<'a> Self::Item<'a>:bivariant_in<'a>;
153
143
```
154
144
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.
157
146
158
147
## Speculative example: `Async` trait
159
148
@@ -196,8 +185,7 @@ impl Frobber for MyType {
196
185
197
186
Joe's original code Just Works.
198
187
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.
201
189
202
190
# Reference-level explanation
203
191
@@ -213,8 +201,7 @@ Implementable trait aliases must follow a more restrictive form:
213
201
214
202
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.
215
203
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.
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
+
traitFoo<T> =PartialEq<T>;
231
+
232
+
// Not implementable
233
+
traitFoo<T> =Copy;
234
+
traitFoo<T> =CopywhereT:Send;
235
+
traitFoo<T> =Iterator<Item=T>;
236
+
traitFoo<T> =CopywhereSelf:PartialEq<T>;
237
+
```
238
+
239
+
Bounds on such generic parameters are enforced at the `impl` site.
If the trait alias uniquely constrains a portion of the `impl` block, that part can be omitted.
240
250
241
251
```rust
@@ -287,8 +297,7 @@ impl Frobber for MyType {
287
297
288
298
Trait aliases are `unsafe` to implement iff the underlying trait is marked `unsafe`.
289
299
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.
292
301
293
302
```rust
294
303
traitIntIter=Iterator<Item=u32> whereSelf:Clone;
@@ -307,23 +316,24 @@ fn foo() {
307
316
308
317
# Drawbacks
309
318
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.
312
320
- On the other hand, the rules mirror those of `impl` blocks, which Rust programmers already understand.
313
321
- 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.
317
324
318
325
# Rationale and alternatives
319
326
320
327
- Very lightweight, with no new syntax forms. Compare "trait transformers" proposals, for example—they are generally much heavier.
321
328
- Better ergonomics compared to purely proc-macro based solutions.
322
329
- 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.
325
335
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?
327
337
328
338
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:
329
339
@@ -370,12 +380,12 @@ Perhaps a more narrowly tailored version of this extension, in which both subtra
370
380
371
381
# Unresolved questions
372
382
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.
375
384
376
385
# Future possibilities
377
386
378
387
- New kinds of bounds: anything that makes `where` clauses more powerful would make this feature more powerful as well.
379
388
- Variance bounds would allow this feature to support backward-compatible GATification.
380
389
- 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