|
| 1 | +- Start Date: 2014-10-10 |
| 2 | +- RFC PR: (leave this empty) |
| 3 | +- Rust Issue: (leave this empty) |
| 4 | + |
| 5 | +# Summary |
| 6 | + |
| 7 | +- Add the ability to have trait bounds that are polymorphic over lifetimes. |
| 8 | + |
| 9 | +# Motivation |
| 10 | + |
| 11 | +Currently, closure types can be polymorphic over lifetimes. But |
| 12 | +closure types are deprecated in favor of traits and object types as |
| 13 | +part of RFC #44 (unboxed closures). We need to close the gap. The |
| 14 | +canonical example of where you want this is if you would like a |
| 15 | +closure that accepts a reference with any lifetime. For example, |
| 16 | +today you might write: |
| 17 | + |
| 18 | + fn with(callback: |&Data|) { |
| 19 | + let data = Data { ... }; |
| 20 | + callback(&data) |
| 21 | + } |
| 22 | + |
| 23 | +If we try to write this using unboxed closures today, we have a problem: |
| 24 | + |
| 25 | + fn with<'a, T>(callback: T) |
| 26 | + where T : FnMut(&'a Data) |
| 27 | + { |
| 28 | + let data = Data { ... }; |
| 29 | + callback(&data) |
| 30 | + } |
| 31 | + |
| 32 | + // Note that the `()` syntax is shorthand for the following: |
| 33 | + fn with<'a, T>(callback: T) |
| 34 | + where T : FnMut<(&'a Data,),()> |
| 35 | + { |
| 36 | + let data = Data { ... }; |
| 37 | + callback(&data) |
| 38 | + } |
| 39 | + |
| 40 | +The problem is that the argument type `&'a Data` must include a |
| 41 | +lifetime, and there is no lifetime one could write in the fn sig that |
| 42 | +represents "the stack frame of the `with` function". Naturally |
| 43 | +we have the same problem if we try to use an `FnMut` object (which is |
| 44 | +the closer analog to the original closure example): |
| 45 | + |
| 46 | + fn with<'a>(callback: &mut FnMut(&'a Data)) |
| 47 | + { |
| 48 | + let data = Data { ... }; |
| 49 | + callback(&data) |
| 50 | + } |
| 51 | + |
| 52 | + fn with<'a>(callback: &mut FnMut<(&'a Data,),()>) |
| 53 | + { |
| 54 | + let data = Data { ... }; |
| 55 | + callback(&data) |
| 56 | + } |
| 57 | + |
| 58 | +Under this proposal, you would be able to write this code as follows: |
| 59 | + |
| 60 | + // Using the FnMut(&Data) notation, the &Data is |
| 61 | + // in fact referencing an implicit bound lifetime, just |
| 62 | + // as with closures today. |
| 63 | + fn with<T>(callback: T) |
| 64 | + where T : FnMut(&Data) |
| 65 | + { |
| 66 | + let data = Data { ... }; |
| 67 | + callback(&data) |
| 68 | + } |
| 69 | + |
| 70 | + // If you prefer, you can use an explicit name, |
| 71 | + // introduced by the `for<'a>` syntax. |
| 72 | + fn with<T>(callback: T) |
| 73 | + where T : for<'a> FnMut(&'a Data) |
| 74 | + { |
| 75 | + let data = Data { ... }; |
| 76 | + callback(&data) |
| 77 | + } |
| 78 | + |
| 79 | + // No sugar at all. |
| 80 | + fn with<T>(callback: T) |
| 81 | + where T : for<'a> FnMut<(&'a Data,),()> |
| 82 | + { |
| 83 | + let data = Data { ... }; |
| 84 | + callback(&data) |
| 85 | + } |
| 86 | + |
| 87 | +And naturally the object form(s) work as well: |
| 88 | + |
| 89 | + // The preferred notation, using `()`, again introduces |
| 90 | + // implicit binders for omitted lifetimes: |
| 91 | + fn with(callback: &mut FnMut(&Data)) |
| 92 | + { |
| 93 | + let data = Data { ... }; |
| 94 | + callback(&data) |
| 95 | + } |
| 96 | + |
| 97 | + // Explicit names work too. |
| 98 | + fn with(callback: &mut for<'a> FnMut(&'a Data)) |
| 99 | + { |
| 100 | + let data = Data { ... }; |
| 101 | + callback(&data) |
| 102 | + } |
| 103 | + |
| 104 | + // The fully explicit notation requires an explicit `for`, |
| 105 | + // as before, to declare the bound lifetimes. |
| 106 | + fn with(callback: &mut for<'a> FnMut<(&'a Data,),()>) |
| 107 | + { |
| 108 | + let data = Data { ... }; |
| 109 | + callback(&data) |
| 110 | + } |
| 111 | + |
| 112 | +The syntax for `fn` types must be updated as well to use `for`. |
| 113 | + |
| 114 | +# Detailed design |
| 115 | + |
| 116 | +## For syntax |
| 117 | + |
| 118 | +We modify the grammar for a trait reference to include |
| 119 | + |
| 120 | + for<lifetimes> Trait<T1, ..., Tn> |
| 121 | + for<lifetimes> Trait(T1, ..., tn) -> Tr |
| 122 | + |
| 123 | +This syntax can be used in where clauses and types. The `for` syntax |
| 124 | +is not permitted in impls nor in qualified paths (`<T as Trait>`). In |
| 125 | +impls, the distinction between early and late-bound lifetimes are |
| 126 | +inferred. In qualified paths, which are used to select a member from |
| 127 | +an impl, no bound lifetimes are permitted. |
| 128 | + |
| 129 | +## Update syntax of fn types |
| 130 | + |
| 131 | +The existing bare fn types will be updated to use the same `for` |
| 132 | +notation. Therefore, `<'a> fn(&'a int)` becomes `for<'a> fn(&'a int)`. |
| 133 | + |
| 134 | +## Implicit binders when using parentheses notation and in fn types |
| 135 | + |
| 136 | +When using the `Trait(T1, ..., Tn)` notation, implicit binders are |
| 137 | +introduced for omitted lifetimes. In other words, `FnMut(&int)` is |
| 138 | +effectively shorthand for `for<'a> FnMut(&'a int)`, which is itself |
| 139 | +shorthand for `for<'a> FnMut<(&'a int,),()>`. No implicit binders are |
| 140 | +introduced when not using the parentheses notation (i.e., |
| 141 | +`Trait<T1,...,Tn>`). These binders interact with lifetime elision in |
| 142 | +the usual way, and hence `FnMut(&Foo) -> &Bar` is shorthand for |
| 143 | +`for<'a> FnMut(&'a Foo) -> &'a Bar`. The same is all true (and already |
| 144 | +true) for fn types. |
| 145 | + |
| 146 | +## Distinguishing early vs late bound lifetimes in impls |
| 147 | + |
| 148 | +We will distinguish early vs late-bound lifetimes on impls in the same |
| 149 | +way as we do for fns. Background on this process can be found in these |
| 150 | +two blog posts \[[1][1], [2][2]\]. The basic idea is to distinguish |
| 151 | +early-bound lifetimes, which must be substituted immediately, from |
| 152 | +late-bound lifetimes, which can be made into a higher-ranked trait |
| 153 | +reference. |
| 154 | + |
| 155 | +The rule is that any lifetime parameter `'x` declared on an impl is |
| 156 | +considered *early bound* if `'x` appears in any of the following locations: |
| 157 | + |
| 158 | +- the self type of the impl; |
| 159 | +- a where clause associated with the impl (here we assume that all bounds on |
| 160 | + impl parameters are desugared into where clauses). |
| 161 | + |
| 162 | +All other lifetimes are considered *late bound*. |
| 163 | + |
| 164 | +When we decide what kind of trait-reference is *provided* by an impl, |
| 165 | +late bound lifetimes are moved into a `for` clause attached to the |
| 166 | +reference. Here are some examples: |
| 167 | + |
| 168 | +```rust |
| 169 | +// Here 'late does not appear in any where clause nor in the self type, |
| 170 | +// and hence it is late-bound. Thus this impl is considered to provide: |
| 171 | +// |
| 172 | +// SomeType : for<'late> FnMut<(&'late Foo,),()> |
| 173 | +impl<'late> FnMut(&'late Foo) -> Bar for SomeType { ... } |
| 174 | + |
| 175 | +// Here 'early appears in the self type and hence it is early bound. |
| 176 | +// This impl thus provides: |
| 177 | +// |
| 178 | +// SomeOtherType<'early> : FnMut<(&'early Foo,),()> |
| 179 | +impl<'early> FnMut(&'early Foo) -> Bar for SomeOtherType<'early> { ... } |
| 180 | +``` |
| 181 | + |
| 182 | +This means that if there were a consumer that required a type which |
| 183 | +implemented `FnMut(&Foo)`, only `SomeType` could be used, not |
| 184 | +`SomeOtherType`: |
| 185 | + |
| 186 | +```rust |
| 187 | +fn foo<T>(t: T) where T : FnMut(&Foo) { ... } |
| 188 | + |
| 189 | +foo::<SomeType>(...) // ok |
| 190 | +foo::<SomeOtherType<'static>>(...) // not ok |
| 191 | +``` |
| 192 | + |
| 193 | +[1]: http://smallcultfollowing.com/babysteps/blog/2013/10/29/intermingled-parameter-lists/ |
| 194 | +[2]: http://smallcultfollowing.com/babysteps/blog/2013/11/04/intermingled-parameter-lists/ |
| 195 | + |
| 196 | +## Instantiating late-bound lifetimes in a trait reference |
| 197 | + |
| 198 | +Whenever |
| 199 | +an associated item from a trait reference is accessed, all late-bound |
| 200 | +lifetimes are instantiated. This means basically when a method is |
| 201 | +called and so forth. Here are some examples: |
| 202 | + |
| 203 | + fn foo<'b,T:for<'a> FnMut(&'a &'b Foo)>(t: T) { |
| 204 | + t(...); // here, 'a is freshly instantiated |
| 205 | + t(...); // here, 'a is freshly instantiated again |
| 206 | + } |
| 207 | + |
| 208 | +Other times when a late-bound lifetime would be instantiated: |
| 209 | + |
| 210 | +- Accessing an associated constant, once those are implemented. |
| 211 | +- Accessing an associated type. |
| 212 | + |
| 213 | +Another way to state these rules is that bound lifetimes are not |
| 214 | +permitted in the traits found in qualified paths -- and things like |
| 215 | +method calls and accesses to associated items can all be desugared |
| 216 | +into calls via qualified paths. For example, the call `t(...)` above |
| 217 | +is equivalent to: |
| 218 | + |
| 219 | + fn foo<'b,T:for<'a> FnMut(&'a &'b Foo)>(t: T) { |
| 220 | + // Here, per the usual rules, the omitted lifetime on the outer |
| 221 | + // reference will be instantiated with a fresh variable. |
| 222 | + <t as FnMut<(&&'b Foo,),()>::call_mut(&mut t, ...); |
| 223 | + <t as FnMut<(&&'b Foo,),()>::call_mut(&mut t, ...); |
| 224 | + } |
| 225 | + |
| 226 | +## Subtyping of trait references |
| 227 | + |
| 228 | +The subtyping rules for trait references that involve higher-ranked |
| 229 | +lifetimes will be defined in an analogous way to the current subtyping |
| 230 | +rules for closures. The high-level idea is to replace each |
| 231 | +higher-ranked lifetime with a skolemized variable, perform the usual |
| 232 | +subtyping checks, and then check whether those skolemized variables |
| 233 | +would be being unified with anything else. The interested reader is |
| 234 | +referred to |
| 235 | +[Simon Peyton-Jones rather thorough but quite readable paper on the topic][spj] |
| 236 | +or the documentation in |
| 237 | +`src/librustc/middle/typeck/infer/region_inference/doc.rs`. |
| 238 | + |
| 239 | +[spj]: http://research.microsoft.com/en-us/um/people/simonpj/papers/higher-rank/ |
| 240 | + |
| 241 | +# Drawbacks |
| 242 | + |
| 243 | +This feature is needed. There isn't really any particular drawback beyond |
| 244 | +language complexity. |
| 245 | + |
| 246 | +# Alternatives |
| 247 | + |
| 248 | +**Drop the keyword.** The `for` keyword is used due to potential |
| 249 | +ambiguities surrounding UFCS notation. Under UFCS, it is legal to |
| 250 | +write e.g. `<T>::Foo::Bar` in a type context. This is awfully close to |
| 251 | +something like `<'a> ::std::FnMut`. Currently, the parser could |
| 252 | +probably use the lifetime distinction to know the difference, but |
| 253 | +future extensions (see next paragraph) could allow types to be used as |
| 254 | +well, and it is still possible we will opt to "drop the tick" in |
| 255 | +lifetimes. Moreover, the syntax `<'a> FnMut(&'a uint)` is not exactly |
| 256 | +beautiful to begin with. |
| 257 | + |
| 258 | +**Permit higher-ranked traits with type variables.** This RFC limits |
| 259 | +"higher-rankedness" to lifetimes. It is plausible to extend the system |
| 260 | +in the future to permit types as well, though only in where clauses |
| 261 | +and not in types. For example, one might write: |
| 262 | + |
| 263 | + fn foo<IDENTITY>(t: IDENTITY) where IDENTITY : for<U> FnMut(U) -> U { ... } |
| 264 | + |
| 265 | +# Unresolved questions |
| 266 | + |
| 267 | +None. Implementation is underway though not complete. |
0 commit comments