|
| 1 | +- Feature Name: visible_trait_where_clause |
| 2 | +- Start Date: (2017-02-24) |
| 3 | +- RFC PR: (leave this empty) |
| 4 | +- Rust Issue: (leave this empty) |
| 5 | + |
| 6 | +# Summary |
| 7 | +[summary]: #summary |
| 8 | + |
| 9 | +Today when you write `T: SomeTrait`, you are also implicitly allowed to rely on |
| 10 | +`T: AnySuperTraits`. However, if `SomeTrait` has additional constraints besides |
| 11 | +supertraits, they are must be repeated every time `T: SomeTrait` is written. |
| 12 | +This RFC allows `T: SomeTrait` to implicitly inherit any constraints of |
| 13 | +`SomeTrait` that are on a concrete type. |
| 14 | + |
| 15 | +# Motivation |
| 16 | +[motivation]: #motivation |
| 17 | + |
| 18 | +For traits which have type parameters, the parameter is often as important as |
| 19 | +the type itself. For example, consider that `T: Into<U>` and `U: From<T>` are |
| 20 | +functionally equivalent. `T` and `U` can be considered to be of equal |
| 21 | +importance. |
| 22 | + |
| 23 | +However, they are not equivalent when it comes to trait definitions. If you were |
| 24 | +to write `trait Foo where Self: From<Bar>`, then writing `T: Foo` implicitly |
| 25 | +also means `T: From<Bar>`. If you change that to the functionally equivalent |
| 26 | +form of `trait Foo where Bar: Into<Self>`, then every place that `T: Foo` is |
| 27 | +written is required to specify that `Bar: Into<T>` as well. There is no reason |
| 28 | +that these should be treated differently. |
| 29 | + |
| 30 | +As a concrete example of where this has caused pain in the wild, Diesel went so |
| 31 | +far as to change a trait from `T: NativeSqlType<DB>` to `DB: HasSqlType<T>`, |
| 32 | +simply so that [they could constraint that a "backend" supports the ANSI |
| 33 | +standard types](https://github.com/diesel-rs/diesel/commit/b2476d1d). That crate |
| 34 | +also has had to avoid adding constraints like `String: FromSql<Text, Self>` as |
| 35 | +doing a similar re-organization would cause coherence issues. |
| 36 | + |
| 37 | +# Detailed design |
| 38 | +[design]: #detailed-design |
| 39 | + |
| 40 | +For the purposes of this RFC, a "constraint" is defined as an item which appears |
| 41 | +in the where clause in the form `target: obligation`. A constraint is considered |
| 42 | +"inherited" if `T: SomeTrait` implicitly adds constraints from the definition of |
| 43 | +`SomeTrait`. |
| 44 | + |
| 45 | +For all examples given, assume that an item has been fully desugared (`trait |
| 46 | +Foo<Bar: Baz>` is expanded to `trait Foo<Bar> where Bar: Baz`, `trait Foo: Bar` |
| 47 | +is expanded to `trait Foo where Self: Bar`, and `where T: Foo + Bar` is expanded |
| 48 | +to `where T: Foo, T: Bar`). |
| 49 | + |
| 50 | +There is no RFC that specifically lays out when constraints are inherited, or |
| 51 | +what the specific reasoning was. However, the design appears to be intended to |
| 52 | +treat traits and structs similarly. Constraints on structs are assumed to be |
| 53 | +based around the type parameters. This is a reasonable assumption for structs, |
| 54 | +since any reference to the `Self` type by definition includes the type |
| 55 | +parameters. |
| 56 | + |
| 57 | +The reasoning behind requiring that `T: SomeTrait<U>` also repeat the |
| 58 | +constraints that `SomeTrait` laid out for `U` is likely to ensure that |
| 59 | +[loosening bounds is not a breaking change](https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md#minor-change-loosening-bounds), |
| 60 | +as any caller relying on those bounds would have had to state them explicitly. |
| 61 | + |
| 62 | +However, traits differ due to the fact that the type of `Self` is not known. |
| 63 | +Referencing `Self` does not necessarily mean that we are referencing the type |
| 64 | +parameters of the trait. For this reason, `T: SomeTrait` inherits any any |
| 65 | +constraints that `SomeTrait` places on `Self`. |
| 66 | + |
| 67 | +Supertraits also differ from normal constraints in that they affect the vtable |
| 68 | +for trait objects. This RFC does not intend to change what is defined as a |
| 69 | +supertrait, or in any way affect trait objects or object safety. This RFC only |
| 70 | +seeks to expand when a constraint is inherited by `T: SomeTrait`. |
| 71 | + |
| 72 | +The current rules for whether a constraint is inherited are simply "the target |
| 73 | +of the constraint is `Self`". This RFC proposes that a constraint is inherited |
| 74 | +when one of the following is true: |
| 75 | + |
| 76 | +- The target of the constraint is `Self` |
| 77 | +- The type parameters of the trait do not appear in the target and `Self` |
| 78 | + appears in either the target or the obligation |
| 79 | + |
| 80 | +Here are some concrete examples (all assume the trait `Foo<T>`) |
| 81 | + |
| 82 | +- `Self: Bar` |
| 83 | + - Before: inherited |
| 84 | + - After: inherited |
| 85 | +- `Self: Bar<T>` |
| 86 | + - Before: inherited |
| 87 | + - After: inherited |
| 88 | +- `T: Bar<Self>` |
| 89 | + - Before: not inherited |
| 90 | + - After: not inherited |
| 91 | +- `Vec<Self>: Bar` |
| 92 | + - Before: not inherited |
| 93 | + - After: inherited |
| 94 | +- `i32: Bar<Self>` |
| 95 | + - Before: not inherited |
| 96 | + - After: inherited |
| 97 | +- `Result<Self, T>: Bar` |
| 98 | + - Before: not inherited |
| 99 | + - After: not inherited |
| 100 | +- `i32: Bar<T, Self>` |
| 101 | + - Before: not inherited |
| 102 | + - After: inherited |
| 103 | + |
| 104 | +This RFC does *not* propose any changes in what is required in the actual trait |
| 105 | +declaration. For example, if writing `trait Foo where Bar: Baz<Self>` required |
| 106 | +that `Self: Sized`, that constraint would still need to be explicitly added to |
| 107 | +the definition of `Foo`. However, `Baz` may have other constraints that fall |
| 108 | +under these new rules, which one could now assume about `Bar`. |
| 109 | + |
| 110 | +# How We Teach This |
| 111 | +[how-we-teach-this]: #how-we-teach-this |
| 112 | + |
| 113 | +The term "constraint" is already pretty well established with the meaning used |
| 114 | +in this RFC. I haven't found the terminology "inherited" used in reference to |
| 115 | +constraints (but actual documentation around them is surprisingly sparse). Since |
| 116 | +this is giving a name to something which currently only occurs for supertraits |
| 117 | +(which emulate a subtype relationship) and lifetime requirements (which is an |
| 118 | +actual subtype relationship), the term "inherited" seems fitting. |
| 119 | + |
| 120 | +This idea is a continuation of an existing Rust pattern, but one that is mostly |
| 121 | +undocumented so would need to be essentially introduced as a new one. |
| 122 | + |
| 123 | +This feature is introduced to new and existing users simply by the compiler |
| 124 | +telling them what to do. Other than concerns regarding backwards compatibility, |
| 125 | +it doesn't need to be explicitly taught. |
| 126 | + |
| 127 | +_The Rust Programming Language_ does not cover supertraits or where clauses, and |
| 128 | +I do not think that we need to add it. _Rust by Example_ would benefit from |
| 129 | +supertraits being mentioned in its section on "bounds", but I do not think it |
| 130 | +needs to lay out the full set of rules. |
| 131 | + |
| 132 | +The Rust Reference does not currently have a section where this addition would |
| 133 | +fit. A new section should be added about constraints in general which covers: |
| 134 | + |
| 135 | +- The implicit `T: Sized` |
| 136 | +- Constraints of structs |
| 137 | +- Supertraits |
| 138 | +- These new rules |
| 139 | + |
| 140 | +# Drawbacks |
| 141 | +[drawbacks]: #drawbacks |
| 142 | + |
| 143 | +This change potentially causes loosening constraints to be considered a major |
| 144 | +change under [RFC #1105]. The RFC currently lists loosening constraints as a |
| 145 | +minor change, but it does so under "Signatures in type definitions" which |
| 146 | +doesn't actually cover traits. Supertraits are never explicitly mentioned in |
| 147 | +that document. If 1105 is interpreted to have removing a supertrait be |
| 148 | +considered a major change, this RFC expands the potential for a major change. |
| 149 | + |
| 150 | +(1105 should be amended to clarify whether removing a supertrait is a major |
| 151 | +change, regardless of whether this RFC is accepted). |
| 152 | + |
| 153 | +[RFC #1105]: https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md |
| 154 | + |
| 155 | +However, there is likely very little code in the wild which has constraints that |
| 156 | +would be inherited by these new rules today. The type of constraint covered by |
| 157 | +this RFC is significantly less useful if it is not inherited. Users who do wish |
| 158 | +to add this sort of constraint in a way that is not inherited can place the |
| 159 | +constraint on specific methods instead of on the trait itself. |
| 160 | + |
| 161 | +# Alternatives |
| 162 | +[alternatives]: #alternatives |
| 163 | + |
| 164 | +We could also have the simpler rule of "target is a type other than `Self` and |
| 165 | +no type parameters from the trait appear in the constraint". However, that |
| 166 | +leaves the case of `Self: Bar<T>` as a weird inconsistency. (It should be noted |
| 167 | +that a constraint that doesn't include the type parameters by definition must |
| 168 | +include `Self` to be valid, otherwise it is simply an assertion which is always |
| 169 | +true or false) |
| 170 | + |
| 171 | +# Unresolved questions |
| 172 | +[unresolved]: #unresolved-questions |
| 173 | + |
| 174 | +None |
0 commit comments