Skip to content

Commit 5ef6311

Browse files
RFC: Implementable trait aliases
1 parent ad4f78f commit 5ef6311

File tree

1 file changed

+319
-0
lines changed

1 file changed

+319
-0
lines changed
Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
1+
- Feature Name: `trait_alias_impl`
2+
- Start Date: 2023-05-24
3+
- RFC PR: [rust-lang/rfcs#3437](https://github.com/rust-lang/rfcs/pull/3437)
4+
- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000)
5+
6+
# Summary
7+
8+
Extend `#![feature(trait_alias)]` to permit `impl` blocks for trait aliases with a single primary trait.
9+
10+
# Motivation
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.
15+
16+
## Example: AFIT `Send` bound aliases
17+
18+
### With subtraits
19+
20+
Imagine a library, `frob-lib`, that provides a trait with an async method. (Think `tower::Service`.)
21+
22+
```rust
23+
//! crate frob-lib
24+
pub trait Frobber {
25+
async fn frob(&self);
26+
}
27+
```
28+
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.
32+
33+
`frob-lib`, following the recommended practice, decides to design its API in the following way:
34+
35+
```rust
36+
//! crate frob-lib
37+
38+
pub trait LocalFrobber {
39+
async fn frob(&self);
40+
}
41+
42+
// or whatever RTN syntax is decided on
43+
pub trait Frobber: LocalFrobber<frob(..): Send> + Send {}
44+
impl<T: ?Sized> Frobber for T where T: LocalFrobber<frob(..): Send> + Send {}
45+
```
46+
47+
These two traits are, in a sense, one trait with two forms: the "weak" `LocalFrobber`, and "strong" `Frobber` that offers an additional `Send` guarantee.
48+
49+
Because `Frobber` (with `Send` bound) is the common case, `frob-lib`'s documentation and examples put it front and center. So naturally, Joe User tries to implement `Frobber` for his own type.
50+
51+
```rust
52+
//! crate joes-crate
53+
use frob_lib::Frobber;
54+
55+
struct MyType;
56+
57+
impl Frobber for MyType {
58+
async fn frob(&self) {
59+
println!("Sloo is 120% klutzed. Initiating brop sequence...")
60+
}
61+
}
62+
```
63+
64+
But one `cargo check` later, Joe is greeted with:
65+
66+
```
67+
error[E0277]: the trait bound `MyType: LocalFrobber` is not satisfied
68+
--> src/lib.rs:6:18
69+
|
70+
6 | impl Frobber for MyType {
71+
| ^^^^^^ the trait `LocalFrobber` is not implemented for `MyType`
72+
73+
error[E0407]: method `frob` is not a member of trait `Frobber`
74+
--> src/lib.rs:7:5
75+
|
76+
7 | / async fn frob(&self) {
77+
8 | | println!("Sloo is 120% klutzed. Initiating brop sequence...")
78+
9 | | }
79+
| |_____^ not a member of trait `Frobber`
80+
```
81+
82+
Joe is confused. "What's a `LocalFrobber`? Isn't that only for non-`Send` use cases? Why do I need to care about all that?" But he eventually figures it out:
83+
84+
```rust
85+
//! crate joes-crate
86+
use frob_lib::LocalFrobber;
87+
88+
struct MyType;
89+
90+
impl LocalFrobber for MyType {
91+
#[refine]
92+
async fn frob(&self) {
93+
println!("Sloo is 120% klutzed. Initiating brop sequence...")
94+
}
95+
}
96+
```
97+
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]`.
100+
101+
### With today's `#![feature(trait_alias)]`
102+
103+
What if `frob-lib` looked like this instead?
104+
105+
```rust
106+
//! crate frob-lib
107+
#![feature(trait_alias)]
108+
109+
pub trait LocalFrobber {
110+
async fn frob(&self);
111+
}
112+
113+
pub trait Frobber = LocalFrobber<frob(..): Send> + Send;
114+
```
115+
116+
With today's `trait_alias`, it wouldn't make much difference for Joe. He would just get a slightly different
117+
error message:
118+
119+
```
120+
error[E0404]: expected trait, found trait alias `Frobber`
121+
--> src/lib.rs:6:6
122+
|
123+
6 | impl Frobber for MyType {
124+
| ^^^^^^^ not a trait
125+
```
126+
127+
## Speculative example: GATification of `Iterator`
128+
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.*
131+
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.
134+
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:
138+
139+
```rust
140+
//! core::iter
141+
pub trait LendingIterator {
142+
type Item<'a>
143+
where
144+
Self: 'a;
145+
146+
fn next(&'a mut self) -> Self::Item<'a>;
147+
}
148+
149+
pub trait Iterator = LendingIterator
150+
where
151+
// speculative syntax, just for the sake of this example
152+
for<'a> Self::Item<'a>: bivariant_in<'a>;
153+
```
154+
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.
157+
158+
## Speculative example: `Async` trait
159+
160+
There has been some discussion about a variant of the `Future` trait with an `unsafe` poll method, to support structured concurrency ([here](https://rust-lang.github.io/wg-async/vision/roadmap/scopes/capability/variant_async_trait.html) for example). *If* such a change ever happens, then the same "weak"/"strong" relationship will arise: the safe-to-poll `Future` trait would be a "strong" version of the unsafe-to-poll `Async`. As the linked design notes explain, there are major problems with expressing this relationship in today's Rust.
161+
162+
# Guide-level explanation
163+
164+
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.
165+
166+
Let's rewrite our AFIT example from before, in terms of this feature. Here's what it looks like now:
167+
168+
```rust
169+
//! crate frob-lib
170+
#![feature(trait_alias)]
171+
172+
pub trait LocalFrobber {
173+
async fn frob(&self);
174+
}
175+
176+
pub trait Frobber = LocalFrobber<frob(..): Send>
177+
where
178+
// not `+ Send`!
179+
Self: Send;
180+
```
181+
182+
```rust
183+
//! crate joes-crate
184+
#![feature(trait_alias_impl)]
185+
186+
use frob_lib::Frobber;
187+
188+
struct MyType;
189+
190+
impl Frobber for MyType {
191+
async fn frob(&self) {
192+
println!("Sloo is 120% klutzed. Initiating brop sequence...")
193+
}
194+
}
195+
```
196+
197+
Joe's original code Just Works.
198+
199+
The rule of thumb is: if you can copy everything between the `=` and `;` of a trait alias, and paste it between the
200+
`for` and `{` of a trait `impl` block, and the result is sytactically valid—then the trait alias is implementable.
201+
202+
# Reference-level explanation
203+
204+
A trait alias has the following syntax (using the Rust Reference's notation):
205+
206+
> [Visibility](https://doc.rust-lang.org/stable/reference/visibility-and-privacy.html)<sup>?</sup> `trait` [IDENTIFIER](https://doc.rust-lang.org/stable/reference/identifiers.html) [GenericParams](https://doc.rust-lang.org/stable/reference/items/generics.html)<sup>?</sup> `=` [TypeParamBounds](https://doc.rust-lang.org/stable/reference/trait-bounds.html)<sup>?</sup> [WhereClause](https://doc.rust-lang.org/stable/reference/items/generics.html#where-clauses)<sup>?</sup> `;`
207+
208+
For example, `trait Foo<T> = PartialEq<T> + Send where Self: Sync;` is a valid trait alias.
209+
210+
Implementable trait aliases must follow a more restrictive form:
211+
212+
> [Visibility](https://doc.rust-lang.org/stable/reference/visibility-and-privacy.html)<sup>?</sup> `trait` [IDENTIFIER](https://doc.rust-lang.org/stable/reference/identifiers.html) [GenericParams](https://doc.rust-lang.org/stable/reference/items/generics.html)<sup>?</sup> `=` [TypePath](https://doc.rust-lang.org/stable/reference/paths.html#paths-in-types) [WhereClause](https://doc.rust-lang.org/stable/reference/items/generics.html#where-clauses)<sup>?</sup> `;`
213+
214+
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+
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.
218+
219+
```rust
220+
pub trait CopyIterator = Iterator<Item: Copy> where Self: Send;
221+
222+
struct Foo;
223+
224+
impl CopyIterator for Foo {
225+
type Item = i32; // Would be an error if this was `String`
226+
227+
fn next(&mut self) -> Self::Item {
228+
42
229+
}
230+
}
231+
232+
struct Bar;
233+
impl !Send for Bar;
234+
235+
// ERROR: `Bar` is not `Send`
236+
// impl IntIterator for Bar { /* ... */ }
237+
```
238+
239+
If the trait alias uniquely constrains a portion of the `impl` block, that part can be omitted.
240+
241+
```rust
242+
pub trait IntIterator = Iterator<Item = i32> where Self: Send;
243+
244+
struct Baz;
245+
246+
impl IntIterator for Baz {
247+
// The alias constrains `Self::Item` to `i32`, so we don't need to specify it
248+
// (though we are allowed to do so if desired).
249+
// type Item = i32;
250+
251+
fn next(&mut self) -> i32 {
252+
-27
253+
}
254+
}
255+
```
256+
257+
Trait aliases also allow omitting implied `#[refine]`s:
258+
259+
```rust
260+
//! crate frob-lib
261+
#![feature(trait_alias)]
262+
263+
pub trait LocalFrobber {
264+
async fn frob(&self);
265+
}
266+
267+
// not `+ Send`!
268+
pub trait Frobber = LocalFrobber<frob(..): Send> where Self: Send;
269+
```
270+
271+
```rust
272+
//! crate joes-crate
273+
#![feature(trait_alias_impl)]
274+
275+
use frob_lib::Frobber;
276+
277+
struct MyType;
278+
279+
impl Frobber for MyType {
280+
// The return future of this method is implicitly `Send`, as implied by the alias.
281+
// No `#[refine]` is necessary.
282+
async fn frob(&self) {
283+
println!("Sloo is 120% klutzed. Initiating brop sequence...")
284+
}
285+
}
286+
```
287+
288+
Trait aliases are `unsafe` to implement iff the underlying trait is marked `unsafe`.
289+
290+
# Drawbacks
291+
292+
- The sytactic distance between implementable and non-implementable aliases is short, which might confuse users.
293+
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.
294+
- Adds complexity to the language.
295+
- Many of the motivating use-cases involve language features that are not yet stable, or even merely speculative.
296+
More experience with those features might unearth better alternatives.
297+
298+
# Rationale and alternatives
299+
300+
- Very lightweight, with no new syntax forms. Compare "trait transformers" proposals, for example—they are generally much heavier.
301+
- Better ergonomics compared to purely proc-macro based solutions.
302+
- One alternative is to allow marker traits or auto traits to appear in `+` bounds of implementable aliases.
303+
(For example, `trait Foo = Bar + Send;` could be made implementable). However, I suspect that the complexity would not be worthwhile.
304+
- Another possibility is to require an attribute on implmenentable aliase; e.g. `#[implementable] trait Foo = ...`. Again, I don't think that the complexity is warranted.
305+
306+
# Prior art
307+
308+
- [`trait_transformer` macro](https://github.com/google/impl_trait_utils)
309+
310+
# Unresolved questions
311+
312+
- How does `rustdoc` render these? Consider the `Frobber` example—ideally, `Frobber` should be emphasized
313+
compared to `LocalFrobber`, but it's not clear how that would work.
314+
315+
# Future possibilities
316+
317+
- New kinds of bounds: anything that makes `where` clauses more powerful would make this feature more powerful as well.
318+
- Variance bounds would allow this feature to support backward-compatible GATification.
319+
- Method unsafety bounds would support the `Future``Async` use-case.

0 commit comments

Comments
 (0)