|
| 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