Skip to content

Commit dc2e147

Browse files
committed
Rough draft.
1 parent c7da23a commit dc2e147

File tree

1 file changed

+267
-0
lines changed

1 file changed

+267
-0
lines changed
Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
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

Comments
 (0)