Skip to content

Commit d81580a

Browse files
committed
Clean up and reorganize traits chapter
1 parent 4b7bdec commit d81580a

File tree

5 files changed

+322
-292
lines changed

5 files changed

+322
-292
lines changed

Diff for: src/SUMMARY.md

+3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
- [The `ty` module: representing types](./ty.md)
1515
- [Type inference](./type-inference.md)
1616
- [Trait resolution](./trait-resolution.md)
17+
- [Higher-ranked trait bounds](./trait-hrtb.md)
18+
- [Caching subtleties](./trait-caching.md)
19+
- [Speciailization](./trait-specialization.md)
1720
- [Type checking](./type-checking.md)
1821
- [The MIR (Mid-level IR)](./mir.md)
1922
- [MIR construction](./mir-construction.md)

Diff for: src/trait-caching.md

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Caching and subtle considerations therewith
2+
3+
In general we attempt to cache the results of trait selection. This
4+
is a somewhat complex process. Part of the reason for this is that we
5+
want to be able to cache results even when all the types in the trait
6+
reference are not fully known. In that case, it may happen that the
7+
trait selection process is also influencing type variables, so we have
8+
to be able to not only cache the *result* of the selection process,
9+
but *replay* its effects on the type variables.
10+
11+
## An example
12+
13+
The high-level idea of how the cache works is that we first replace
14+
all unbound inference variables with skolemized versions. Therefore,
15+
if we had a trait reference `usize : Foo<$1>`, where `$n` is an unbound
16+
inference variable, we might replace it with `usize : Foo<%0>`, where
17+
`%n` is a skolemized type. We would then look this up in the cache.
18+
If we found a hit, the hit would tell us the immediate next step to
19+
take in the selection process: i.e. apply impl #22, or apply where
20+
clause `X : Foo<Y>`. Let's say in this case there is no hit.
21+
Therefore, we search through impls and where clauses and so forth, and
22+
we come to the conclusion that the only possible impl is this one,
23+
with def-id 22:
24+
25+
```rust
26+
impl Foo<isize> for usize { ... } // Impl #22
27+
```
28+
29+
We would then record in the cache `usize : Foo<%0> ==>
30+
ImplCandidate(22)`. Next we would confirm `ImplCandidate(22)`, which
31+
would (as a side-effect) unify `$1` with `isize`.
32+
33+
Now, at some later time, we might come along and see a `usize :
34+
Foo<$3>`. When skolemized, this would yield `usize : Foo<%0>`, just as
35+
before, and hence the cache lookup would succeed, yielding
36+
`ImplCandidate(22)`. We would confirm `ImplCandidate(22)` which would
37+
(as a side-effect) unify `$3` with `isize`.
38+
39+
## Where clauses and the local vs global cache
40+
41+
One subtle interaction is that the results of trait lookup will vary
42+
depending on what where clauses are in scope. Therefore, we actually
43+
have *two* caches, a local and a global cache. The local cache is
44+
attached to the `ParamEnv` and the global cache attached to the
45+
`tcx`. We use the local cache whenever the result might depend on the
46+
where clauses that are in scope. The determination of which cache to
47+
use is done by the method `pick_candidate_cache` in `select.rs`. At
48+
the moment, we use a very simple, conservative rule: if there are any
49+
where-clauses in scope, then we use the local cache. We used to try
50+
and draw finer-grained distinctions, but that led to a serious of
51+
annoying and weird bugs like #22019 and #18290. This simple rule seems
52+
to be pretty clearly safe and also still retains a very high hit rate
53+
(~95% when compiling rustc).
54+

Diff for: src/trait-hrtb.md

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# Higher-ranked trait bounds
2+
3+
One of the more subtle concepts at work are *higher-ranked trait
4+
bounds*. An example of such a bound is `for<'a> MyTrait<&'a isize>`.
5+
Let's walk through how selection on higher-ranked trait references
6+
works.
7+
8+
## Basic matching and skolemization leaks
9+
10+
Let's walk through the test `compile-fail/hrtb-just-for-static.rs` to see
11+
how it works. The test starts with the trait `Foo`:
12+
13+
```rust
14+
trait Foo<X> {
15+
fn foo(&self, x: X) { }
16+
}
17+
```
18+
19+
Let's say we have a function `want_hrtb` that wants a type which
20+
implements `Foo<&'a isize>` for any `'a`:
21+
22+
```rust
23+
fn want_hrtb<T>() where T : for<'a> Foo<&'a isize> { ... }
24+
```
25+
26+
Now we have a struct `AnyInt` that implements `Foo<&'a isize>` for any
27+
`'a`:
28+
29+
```rust
30+
struct AnyInt;
31+
impl<'a> Foo<&'a isize> for AnyInt { }
32+
```
33+
34+
And the question is, does `AnyInt : for<'a> Foo<&'a isize>`? We want the
35+
answer to be yes. The algorithm for figuring it out is closely related
36+
to the subtyping for higher-ranked types (which is described in
37+
`middle::infer::higher_ranked::doc`, but also in a [paper by SPJ] that
38+
I recommend you read).
39+
40+
1. Skolemize the obligation.
41+
2. Match the impl against the skolemized obligation.
42+
3. Check for skolemization leaks.
43+
44+
[paper by SPJ]: http://research.microsoft.com/en-us/um/people/simonpj/papers/higher-rank/
45+
46+
So let's work through our example. The first thing we would do is to
47+
skolemize the obligation, yielding `AnyInt : Foo<&'0 isize>` (here `'0`
48+
represents skolemized region #0). Note that now have no quantifiers;
49+
in terms of the compiler type, this changes from a `ty::PolyTraitRef`
50+
to a `TraitRef`. We would then create the `TraitRef` from the impl,
51+
using fresh variables for it's bound regions (and thus getting
52+
`Foo<&'$a isize>`, where `'$a` is the inference variable for `'a`). Next
53+
we relate the two trait refs, yielding a graph with the constraint
54+
that `'0 == '$a`. Finally, we check for skolemization "leaks" – a
55+
leak is basically any attempt to relate a skolemized region to another
56+
skolemized region, or to any region that pre-existed the impl match.
57+
The leak check is done by searching from the skolemized region to find
58+
the set of regions that it is related to in any way. This is called
59+
the "taint" set. To pass the check, that set must consist *solely* of
60+
itself and region variables from the impl. If the taint set includes
61+
any other region, then the match is a failure. In this case, the taint
62+
set for `'0` is `{'0, '$a}`, and hence the check will succeed.
63+
64+
Let's consider a failure case. Imagine we also have a struct
65+
66+
```rust
67+
struct StaticInt;
68+
impl Foo<&'static isize> for StaticInt;
69+
```
70+
71+
We want the obligation `StaticInt : for<'a> Foo<&'a isize>` to be
72+
considered unsatisfied. The check begins just as before. `'a` is
73+
skolemized to `'0` and the impl trait reference is instantiated to
74+
`Foo<&'static isize>`. When we relate those two, we get a constraint
75+
like `'static == '0`. This means that the taint set for `'0` is `{'0,
76+
'static}`, which fails the leak check.
77+
78+
## Higher-ranked trait obligations
79+
80+
Once the basic matching is done, we get to another interesting topic:
81+
how to deal with impl obligations. I'll work through a simple example
82+
here. Imagine we have the traits `Foo` and `Bar` and an associated impl:
83+
84+
```rust
85+
trait Foo<X> {
86+
fn foo(&self, x: X) { }
87+
}
88+
89+
trait Bar<X> {
90+
fn bar(&self, x: X) { }
91+
}
92+
93+
impl<X,F> Foo<X> for F
94+
where F : Bar<X>
95+
{
96+
}
97+
```
98+
99+
Now let's say we have a obligation `for<'a> Foo<&'a isize>` and we match
100+
this impl. What obligation is generated as a result? We want to get
101+
`for<'a> Bar<&'a isize>`, but how does that happen?
102+
103+
After the matching, we are in a position where we have a skolemized
104+
substitution like `X => &'0 isize`. If we apply this substitution to the
105+
impl obligations, we get `F : Bar<&'0 isize>`. Obviously this is not
106+
directly usable because the skolemized region `'0` cannot leak out of
107+
our computation.
108+
109+
What we do is to create an inverse mapping from the taint set of `'0`
110+
back to the original bound region (`'a`, here) that `'0` resulted
111+
from. (This is done in `higher_ranked::plug_leaks`). We know that the
112+
leak check passed, so this taint set consists solely of the skolemized
113+
region itself plus various intermediate region variables. We then walk
114+
the trait-reference and convert every region in that taint set back to
115+
a late-bound region, so in this case we'd wind up with `for<'a> F :
116+
Bar<&'a isize>`.

0 commit comments

Comments
 (0)