Skip to content

Commit aa20e2f

Browse files
committed
Document new algorithm at a high-level.
1 parent 0b88c5d commit aa20e2f

File tree

4 files changed

+121
-6
lines changed

4 files changed

+121
-6
lines changed

src/librustc/middle/traits/doc.rs

Lines changed: 117 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,123 @@ nested obligation `int : Bar<U>` to find out that `U=uint`.
272272
It would be good to only do *just as much* nested resolution as
273273
necessary. Currently, though, we just do a full resolution.
274274
275+
# Higher-ranked trait bounds
276+
277+
One of the more subtle concepts at work are *higher-ranked trait
278+
bounds*. An example of such a bound is `for<'a> MyTrait<&'a int>`.
279+
Let's walk through how selection on higher-ranked trait references
280+
works.
281+
282+
## Basic matching and skolemization leaks
283+
284+
Let's walk through the test `compile-fail/hrtb-just-for-static.rs` to see
285+
how it works. The test starts with the trait `Foo`:
286+
287+
```rust
288+
trait Foo<X> {
289+
fn foo(&self, x: X) { }
290+
}
291+
```
292+
293+
Let's say we have a function `want_hrtb` that wants a type which
294+
implements `Foo<&'a int>` for any `'a`:
295+
296+
```rust
297+
fn want_hrtb<T>() where T : for<'a> Foo<&'a int> { ... }
298+
```
299+
300+
Now we have a struct `AnyInt` that implements `Foo<&'a int>` for any
301+
`'a`:
302+
303+
```rust
304+
struct AnyInt;
305+
impl<'a> Foo<&'a int> for AnyInt { }
306+
```
307+
308+
And the question is, does `AnyInt : for<'a> Foo<&'a int>`? We want the
309+
answer to be yes. The algorithm for figuring it out is closely related
310+
to the subtyping for higher-ranked types (which is described in
311+
`middle::infer::higher_ranked::doc`, but also in a [paper by SPJ] that
312+
I recommend you read).
313+
314+
1. Skolemize the obligation.
315+
2. Match the impl against the skolemized obligation.
316+
3. Check for skolemization leaks.
317+
318+
[paper by SPJ]: http://research.microsoft.com/en-us/um/people/simonpj/papers/higher-rank/
319+
320+
So let's work through our example. The first thing we would do is to
321+
skolemize the obligation, yielding `AnyInt : Foo<&'0 int>` (here `'0`
322+
represents skolemized region #0). Note that now have no quantifiers;
323+
in terms of the compiler type, this changes from a `ty::PolyTraitRef`
324+
to a `TraitRef`. We would then create the `TraitRef` from the impl,
325+
using fresh variables for it's bound regions (and thus getting
326+
`Foo<&'$a int>`, where `'$a` is the inference variable for `'a`). Next
327+
we relate the two trait refs, yielding a graph with the constraint
328+
that `'0 == '$a`. Finally, we check for skolemization "leaks" -- a
329+
leak is basically any attempt to relate a skolemized region to another
330+
skolemized region, or to any region that pre-existed the impl match.
331+
The leak check is done by searching from the skolemized region to find
332+
the set of regions that it is related to in any way. This is called
333+
the "taint" set. To pass the check, that set must consist *solely* of
334+
itself and region variables from the impl. If the taint set includes
335+
any other region, then the match is a failure. In this case, the taint
336+
set for `'0` is `{'0, '$a}`, and hence the check will succeed.
337+
338+
Let's consider a failure case. Imagine we also have a struct
339+
340+
```rust
341+
struct StaticInt;
342+
impl Foo<&'static int> for StaticInt;
343+
```
344+
345+
We want the obligation `StaticInt : for<'a> Foo<&'a int>` to be
346+
considered unsatisfied. The check begins just as before. `'a` is
347+
skolemized to `'0` and the impl trait reference is instantiated to
348+
`Foo<&'static int>`. When we relate those two, we get a constraint
349+
like `'static == '0`. This means that the taint set for `'0` is `{'0,
350+
'static}`, which fails the leak check.
351+
352+
## Higher-ranked trait obligations
353+
354+
Once the basic matching is done, we get to another interesting topic:
355+
how to deal with impl obligations. I'll work through a simple example
356+
here. Imagine we have the traits `Foo` and `Bar` and an associated impl:
357+
358+
```
359+
trait Foo<X> {
360+
fn foo(&self, x: X) { }
361+
}
362+
363+
trait Bar<X> {
364+
fn bar(&self, x: X) { }
365+
}
366+
367+
impl<X,F> Foo<X> for F
368+
where F : Bar<X>
369+
{
370+
}
371+
```
372+
373+
Now let's say we have a obligation `for<'a> Foo<&'a int>` and we match
374+
this impl. What obligation is generated as a result? We want to get
375+
`for<'a> Bar<&'a int>`, but how does that happen?
376+
377+
After the matching, we are in a position where we have a skolemized
378+
substitution like `X => &'0 int`. If we apply this substitution to the
379+
impl obligations, we get `F : Bar<&'0 int>`. Obviously this is not
380+
directly usable because the skolemized region `'0` cannot leak out of
381+
our computation.
382+
383+
What we do is to create an inverse mapping from the taint set of `'0`
384+
back to the original bound region (`'a`, here) that `'0` resulted
385+
from. (This is done in `higher_ranked::plug_leaks`). We know that the
386+
leak check passed, so this taint set consists solely of the skolemized
387+
region itself plus various intermediate region variables. We then walk
388+
the trait-reference and convert every region in that taint set back to
389+
a late-bound region, so in this case we'd wind up with `for<'a> F :
390+
Bar<&'a int>`.
391+
275392
# Caching and subtle considerations therewith
276393
277394
In general we attempt to cache the results of trait selection. This
@@ -400,6 +517,4 @@ there is no other type the user could enter. However, it is not
400517
future; we wouldn't have to guess types, in particular, we could be
401518
led by the impls.
402519
403-
404-
405520
*/

src/test/compile-fail/hrtb-conflate-regions.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11-
// Test what an impl with only one bound region `'a` cannot be used to
12-
// satisfy a constraint whre there are two bound regions.
11+
// Test that an impl with only one bound region `'a` cannot be used to
12+
// satisfy a constraint where there are two bound regions.
1313

1414
trait Foo<X> {
1515
fn foo(&self, x: X) { }

src/test/compile-fail/hrtb-just-for-static.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ fn give_any() {
2727
want_hrtb::<AnyInt>()
2828
}
2929

30-
// StaticInt only implements Foo<&'a int> for 'a, so it is an error.
30+
// StaticInt only implements Foo<&'static int>, so it is an error.
3131
struct StaticInt;
3232
impl Foo<&'static int> for StaticInt { }
3333
fn give_static() {

src/test/compile-fail/hrtb-type-outlives.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11-
// Test what happens when a HR obligation is applie to an impl with
11+
// Test what happens when a HR obligation is applied to an impl with
1212
// "outlives" bounds. Currently we're pretty conservative here; this
1313
// will probably improve in time.
1414

0 commit comments

Comments
 (0)