Skip to content

Commit 24f15c1

Browse files
Rewrite AFIT example in terms of trait-variant
1 parent 601cc9c commit 24f15c1

File tree

1 file changed

+120
-98
lines changed

1 file changed

+120
-98
lines changed

text/3437-implementable-trait-alias.md

Lines changed: 120 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -21,127 +21,154 @@ relationships with the following properties:
2121
implement the "strong" variant.
2222

2323
Subtrait relationships are commonly used to model this, but this often leads to
24-
coherence and backward compatibility issues—especially when the "strong" version
25-
is expected to see more use, or was stabilized first.
24+
coherence and backward compatibility issues.
2625

27-
## Example: AFIT `Send` bound aliases
28-
29-
### With subtraits
26+
## AFIT `Send` bound aliases
3027

3128
Imagine a library, `frob-lib`, that provides a trait with an async method.
32-
(Think `tower::Service`.)
3329

3430
```rust
35-
//! crate frob-lib
36-
pub trait Frobber {
31+
//! crate `frob-lib`
32+
pub trait Frob {
3733
async fn frob(&self);
3834
}
3935
```
4036

41-
Most of `frob-lib`'s users will need `Frobber::frob`'s return type to be `Send`,
37+
Most of `frob-lib`'s users will need `Frob::frob`'s return type to be `Send`,
4238
so the library wants to make this common case as painless as possible. But
4339
non-`Send` usage should be supported as well.
4440

45-
`frob-lib`, following the recommended practice, decides to design its API in the
46-
following way:
41+
### MVP: `trait_variant`
42+
43+
Because Return Type Notation isn't supported yet, `frob-lib` follows the
44+
recommended practice of using the [`trait-variant`](https://docs.rs/trait-variant/)
45+
crate to have `Send` and non-`Send` variants.
4746

4847
```rust
49-
//! crate frob-lib
48+
//! crate `frob-lib``
5049

51-
pub trait LocalFrobber {
52-
async fn frob(&self);
50+
#[trait_variant::make(Frob: Send)]
51+
pub trait LocalFrob {
52+
async fn frob(&mut self);
5353
}
54-
55-
// or whatever RTN syntax is decided on
56-
pub trait Frobber: LocalFrobber<frob(..): Send> + Send {}
57-
impl<T: ?Sized> Frobber for T where T: LocalFrobber<frob(..): Send> + Send {}
5854
```
5955

60-
These two traits are, in a sense, one trait with two forms: the "weak"
61-
`LocalFrobber`, and "strong" `Frobber` that offers an additional `Send`
62-
guarantee.
63-
64-
Because `Frobber` (with `Send` bound) is the common case, `frob-lib`'s
65-
documentation and examples put it front and center. So naturally, Joe User tries
66-
to implement `Frobber` for his own type.
56+
However, this API has limitations. Fox example, `frob-lib` may want to offer a
57+
`DoubleFrob` wrapper:
6758

6859
```rust
69-
//! crate joes-crate
70-
use frob_lib::Frobber;
60+
pub struct DoubleFrob<T: LocalFrob>(T);
7161

72-
struct MyType;
62+
impl<T: LocalFrob> LocalFrob for DoubleFrob<T> {
63+
async fn frob(&mut self) {
64+
self.0.frob().await;
65+
self.0.frob().await;
66+
}
67+
}
68+
```
69+
70+
As written, this wrapper only implements `LocalFrob`, which means that it's not
71+
fully compatible with work-stealing executors. So `frob-lib` tries to add a
72+
`Frob` implementation as well:
7373

74-
impl Frobber for MyType {
75-
async fn frob(&self) {
76-
println!("Sloo is 120% klutzed. Initiating brop sequence...")
74+
```rust
75+
impl<T: Frob> Frob for DoubleFrob<T> {
76+
async fn frob(&mut self) {
77+
self.0.frob().await;
78+
self.0.frob().await;
7779
}
7880
}
7981
```
8082

81-
But one `cargo check` later, Joe is greeted with:
83+
Coherence, however, rejects this.
8284

8385
```
84-
error[E0277]: the trait bound `MyType: LocalFrobber` is not satisfied
85-
--> src/lib.rs:6:18
86-
|
87-
6 | impl Frobber for MyType {
88-
| ^^^^^^ the trait `LocalFrobber` is not implemented for `MyType`
86+
error[E0119]: conflicting implementations of trait `LocalFrob` for type `DoubleFrob<_>`
87+
--> src/lib.rs:1:1
88+
|
89+
1 | #[trait_variant::make(Frob: Send)]
90+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `DoubleFrob<_>`
91+
...
92+
8 | impl<T: LocalFrob> LocalFrob for DoubleFrob<T> {
93+
| ---------------------------------------------- first implementation here
94+
|
95+
= note: this error originates in the attribute macro `trait_variant::make` (in Nightly builds, run with -Z macro-backtrace for more info)
8996
90-
error[E0407]: method `frob` is not a member of trait `Frobber`
91-
--> src/lib.rs:7:5
92-
|
93-
7 | / async fn frob(&self) {
94-
8 | | println!("Sloo is 120% klutzed. Initiating brop sequence...")
95-
9 | | }
96-
| |_____^ not a member of trait `Frobber`
97+
For more information about this error, try `rustc --explain E0119`.
9798
```
9899

99-
Joe is confused. "What's a `LocalFrobber`? Isn't that only for non-`Send` use
100-
cases? Why do I need to care about all that?" But he eventually figures it out:
100+
With the `trait_variant`-based design, it's impossible to support both `Send`
101+
and non-`Send` usage in the same `DoubleFrob` type.
102+
103+
### Migrating to Return Type Notation
104+
105+
A few Rust releases later, Return Type Notation is stabilized. `frob-lib` wants
106+
to migrate to it in order to address the issues with the `trait_variant`
107+
solution:
101108

102109
```rust
103-
//! crate joes-crate
104-
use frob_lib::LocalFrobber;
110+
//! crate `frob-lib``
111+
112+
pub trait LocalFrob {
113+
async fn frob(&self);
114+
}
115+
116+
// or whatever RTN syntax is decided on
117+
pub trait Frob: LocalFrob<frob(..): Send> + Send {}
118+
impl<T: ?Sized> Frob for T where T: LocalFrob<frob(..): Send> + Send {}
119+
```
120+
121+
However, this is an incompatible change; all implementations of `Frob` are
122+
broken!
123+
124+
```rust
125+
//! crate `downstream`
126+
use frob_lib::Frob;
105127

106128
struct MyType;
107129

108-
impl LocalFrobber for MyType {
109-
async fn frob(&self) {
110-
println!("Sloo is 120% klutzed. Initiating brop sequence...")
111-
}
130+
impl Frob for MyType {
131+
// Now an error, "trait `Frob` has no method `frob`"
132+
async fn frob(&self) { /* ... */ }
133+
}
134+
```
135+
136+
All `impl` blocks for `Frob` must be migrated to reference `LocalFrob` instead.
137+
138+
```rust
139+
//! crate `downstream`
140+
use frob_lib::LocalFrob;
141+
142+
struct MyType;
143+
144+
impl LocalFrob for MyType {
145+
async fn frob(&self) { /* ... */ }
112146
}
113147
```
114148

115-
This is distinctly worse; Joe now has to reference both `Frobber` and
116-
`LocalFrobber` in his code.
149+
Not only is this change disruptive, it also results in more confusing code.
150+
`downstream` is written for work-stealing executors, but needs to reference
151+
`LocalFrob` anyway.
117152

118153
### With today's `#![feature(trait_alias)]`
119154

120155
What if `frob-lib` looked like this instead?
121156

122157
```rust
123-
//! crate frob-lib
158+
//! crate `frob-lib`
124159
#![feature(trait_alias)]
125160

126-
pub trait LocalFrobber {
161+
pub trait LocalFrob {
127162
async fn frob(&self);
128163
}
129164

130-
pub trait Frobber = LocalFrobber<frob(..): Send> + Send;
165+
pub trait Frob = LocalFrob<frob(..): Send> + Send;
131166
```
132167

133-
With today's `trait_alias`, it wouldn't make much difference for Joe. He would
134-
just get a slightly different error message:
168+
With today's `trait_alias`, it wouldn't make much difference for `downstream`.
169+
`impl` blocks for `Frob` would still be broken.
135170

136-
```
137-
error[E0404]: expected trait, found trait alias `Frobber`
138-
--> src/lib.rs:6:6
139-
|
140-
6 | impl Frobber for MyType {
141-
| ^^^^^^^ not a trait
142-
```
143-
144-
## Speculative example: GATification of `Iterator`
171+
## (Speculative) GATification of `Iterator`
145172

146173
*This example relies on some language features that are currently pure
147174
speculation. Implementable trait aliases are potentially necessary to support
@@ -160,7 +187,7 @@ parameters. One could then define `Iterator` in terms of `LendingIterator`, like
160187
so:
161188

162189
```rust
163-
//! core::iter
190+
//! `core::iter`
164191
pub trait LendingIterator {
165192
type Item<'a>
166193
where
@@ -179,58 +206,56 @@ But, as with the previous example, we are foiled by the fact that trait aliases
179206
aren't `impl`ementable, so this change would break every `impl Iterator` block
180207
in existence.
181208

182-
## Speculative example: `Async` trait
209+
## (Speculative) `Async` trait
183210

184211
There has been some discussion about a variant of the `Future` trait with an
185-
`unsafe` poll method, to support structured concurrency ([here](https://rust-lang.github.io/wg-async/vision/roadmap/scopes/capability/variant_async_trait.html)
186-
for example). *If* such a change ever happens, then the same "weak"/"strong"
187-
relationship will arise: the safe-to-poll `Future` trait would be a "strong"
188-
version of the unsafe-to-poll `Async`. As the linked design notes explain, there
189-
are major problems with expressing that relationship in today's Rust.
212+
`unsafe` poll method, to support structured concurrency ([wg-async design notes](https://rust-lang.github.io/wg-async/vision/roadmap/scopes/capability/variant_async_trait.html)).
213+
*If* such a change ever happens, then the same "weak"/"strong" relationship will
214+
arise: the safe-to-poll `Future` trait would be a "strong" version of the
215+
unsafe-to-poll `Async`. As the linked design notes explain, there are major
216+
problems with expressing that relationship in today's Rust.
190217

191218
# Guide-level explanation
192219

193220
With `#![feature(trait_alias)]` (RFC #1733), one can define trait aliases, for
194221
use in bounds, trait objects, and `impl Trait`. This feature additionally allows
195222
writing `impl` blocks for a subset of trait aliases.
196223

197-
Let's rewrite our AFIT example from before, in terms of this feature. Here's
198-
what it looks like now:
224+
Let's rewrite our AFIT example from before using this feature. Here's what it
225+
looks like now:
199226

200227
```rust
201-
//! crate frob-lib
228+
//! crate `frob-lib`
202229
#![feature(trait_alias)]
203230

204-
pub trait LocalFrobber {
231+
pub trait LocalFrob {
205232
async fn frob(&self);
206233
}
207234

208-
pub trait Frobber = LocalFrobber<frob(..): Send>
235+
pub trait Frob = LocalFrob<frob(..): Send>
209236
where
210237
// not `+ Send`!
211238
Self: Send;
212239
```
213240

214241
```rust
215-
//! crate joes-crate
242+
//! crate `downstream`
216243
#![feature(trait_alias_impl)]
217244

218-
use frob_lib::Frobber;
245+
use frob_lib::Frob;
219246

220247
struct MyType;
221248

222-
impl Frobber for MyType {
223-
async fn frob(&self) {
224-
println!("Sloo is 120% klutzed. Initiating brop sequence...")
225-
}
249+
impl Frob for MyType {
250+
async fn frob(&self) { /* ... */ }
226251
}
227252
```
228253

229-
Joe's original code Just Works.
254+
`impl`s of `Frob` now Just Work.
230255

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

235260
# Reference-level explanation
236261

@@ -323,7 +348,6 @@ struct Baz;
323348
impl IntIterator for Baz {
324349
// The alias constrains `Self::Item` to `i32`, so we don't need to specify it
325350
// (though we are allowed to do so if desired).
326-
// type Item = i32;
327351

328352
fn next(&mut self) -> i32 {
329353
-27
@@ -359,28 +383,26 @@ Alias `impl`s also allow omitting implied `#[refine]`s:
359383
//! crate frob-lib
360384
#![feature(trait_alias)]
361385

362-
pub trait LocalFrobber {
386+
pub trait LocalFrob {
363387
async fn frob(&self);
364388
}
365389

366390
// not `+ Send`!
367-
pub trait Frobber = LocalFrobber<frob(..): Send> where Self: Send;
391+
pub trait Frob = LocalFrob<frob(..): Send> where Self: Send;
368392
```
369393

370394
```rust
371395
//! crate joes-crate
372396
#![feature(trait_alias_impl)]
373397

374-
use frob_lib::Frobber;
398+
use frob_lib::Frob;
375399

376400
struct MyType;
377401

378-
impl Frobber for MyType {
402+
impl Frob for MyType {
379403
// The return future of this method is implicitly `Send`, as implied by the alias.
380404
// No `#[refine]` is necessary.
381-
async fn frob(&self) {
382-
println!("Sloo is 120% klutzed. Initiating brop sequence...")
383-
}
405+
async fn frob(&self) { /* ... */ }
384406
}
385407
```
386408

@@ -495,8 +517,8 @@ even at the risk of potential confusion.
495517

496518
# Unresolved questions
497519

498-
- How does `rustdoc` render these? Consider the `Frobber` example—ideally,
499-
`Frobber` should be emphasized compared to `LocalFrobber`, but it's not clear
520+
- How does `rustdoc` render these? Consider the `Frob` example—ideally,
521+
`Frob` should be emphasized compared to `LocalFrob`, but it's not clear
500522
how that would work.
501523

502524
# Future possibilities

0 commit comments

Comments
 (0)