Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit d81580a

Browse files
committedFeb 12, 2018
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
 

‎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)

‎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+

‎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>`.

‎src/trait-resolution.md

+110-292
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
# Trait resolution
22

3-
This document describes the general process and points out some non-obvious
4-
things.
5-
6-
**WARNING:** This material was moved verbatim from a rustc README, so
7-
it may not "fit" the style of the guide until it is adapted.
3+
This chapter describes the general process of _trait resolution_ and points out
4+
some non-obvious things.
85

96
## Major concepts
107

@@ -21,12 +18,12 @@ and then a call to that function:
2118
let v: Vec<isize> = clone_slice(&[1, 2, 3])
2219
```
2320

24-
it is the job of trait resolution to figure out (in which case)
25-
whether there exists an impl of `isize : Clone`
21+
it is the job of trait resolution to figure out whether there exists an impl of
22+
(in this case) `isize : Clone`.
2623

2724
Note that in some cases, like generic functions, we may not be able to
2825
find a specific impl, but we can figure out that the caller must
29-
provide an impl. To see what I mean, consider the body of `clone_slice`:
26+
provide an impl. For example, consider the body of `clone_slice`:
3027

3128
```rust
3229
fn clone_slice<T:Clone>(x: &[T]) -> Vec<T> {
@@ -43,26 +40,33 @@ is, we can't find the specific impl; but based on the bound `T:Clone`,
4340
we can say that there exists an impl which the caller must provide.
4441

4542
We use the term *obligation* to refer to a trait reference in need of
46-
an impl.
43+
an impl. Basically, the trait resolution system resolves an obligation
44+
by proving that an appropriate impl does exist.
45+
46+
During type checking, we do not store the results of trait selection.
47+
We simply wish to verify that trait selection will succeed. Then
48+
later, at trans time, when we have all concrete types available, we
49+
can repeat the trait selection to choose an actual implementation, which
50+
will then be generated in the output binary.
4751

4852
## Overview
4953

5054
Trait resolution consists of three major parts:
5155

52-
- SELECTION: Deciding how to resolve a specific obligation. For
56+
- **Selection** is deciding how to resolve a specific obligation. For
5357
example, selection might decide that a specific obligation can be
54-
resolved by employing an impl which matches the self type, or by
55-
using a parameter bound. In the case of an impl, Selecting one
58+
resolved by employing an impl which matches the `Self` type, or by
59+
using a parameter bound (e.g. `T: Trait`). In the case of an impl, selecting one
5660
obligation can create *nested obligations* because of where clauses
5761
on the impl itself. It may also require evaluating those nested
5862
obligations to resolve ambiguities.
5963

60-
- FULFILLMENT: The fulfillment code is what tracks that obligations
61-
are completely fulfilled. Basically it is a worklist of obligations
64+
- **Fulfillment** is keeping track of which obligations
65+
are completely fulfilled. Basically, it is a worklist of obligations
6266
to be selected: once selection is successful, the obligation is
6367
removed from the worklist and any nested obligations are enqueued.
6468

65-
- COHERENCE: The coherence checks are intended to ensure that there
69+
- **Coherence** checks are intended to ensure that there
6670
are never overlapping impls, where two impls could be used with
6771
equal precedence.
6872

@@ -83,12 +87,21 @@ and returns a `SelectionResult`. There are three possible outcomes:
8387
contains unbound type variables.
8488

8589
- `Err(err)` – the obligation definitely cannot be resolved due to a
86-
type error, or because there are no impls that could possibly apply,
87-
etc.
90+
type error or because there are no impls that could possibly apply.
8891

8992
The basic algorithm for selection is broken into two big phases:
9093
candidate assembly and confirmation.
9194

95+
Note that because of how lifetime inference works, it is not possible to
96+
give back immediate feedback as to whether a unification or subtype
97+
relationship between lifetimes holds or not. Therefore, lifetime
98+
matching is *not* considered during selection. This is reflected in
99+
the fact that subregion assignment is infallible. This may yield
100+
lifetime constraints that will later be found to be in error (in
101+
contrast, the non-lifetime-constraints have already been checked
102+
during selection and can never cause an error, though naturally they
103+
may lead to other errors downstream).
104+
92105
### Candidate assembly
93106

94107
Searches for impls/where-clauses/etc that might
@@ -98,6 +111,15 @@ candidate that is definitively applicable. In some cases, we may not
98111
know whether an impl/where-clause applies or not – this occurs when
99112
the obligation contains unbound inference variables.
100113

114+
The subroutines that decide whether a particular impl/where-clause/etc
115+
applies to a particular obligation are collectively refered to as the
116+
process of _matching_. At the moment, this amounts to
117+
unifying the `Self` types, but in the future we may also recursively
118+
consider some of the nested obligations, in the case of an impl.
119+
120+
**TODO**: what does "unifying the `Self` types" mean? The `Self` of the
121+
obligation with that of an impl?
122+
101123
The basic idea for candidate assembly is to do a first pass in which
102124
we identify all possible candidates. During this pass, all that we do
103125
is try and unify the type parameters. (In particular, we ignore any
@@ -141,16 +163,14 @@ let y = x.convert();
141163

142164
The call to convert will generate a trait reference `Convert<$Y> for
143165
isize`, where `$Y` is the type variable representing the type of
144-
`y`. When we match this against the two impls we can see, we will find
145-
that only one remains: `Convert<usize> for isize`. Therefore, we can
166+
`y`. Of the two impls we can see, the only one that matches is
167+
`Convert<usize> for isize`. Therefore, we can
146168
select this impl, which will cause the type of `$Y` to be unified to
147169
`usize`. (Note that while assembling candidates, we do the initial
148170
unifications in a transaction, so that they don't affect one another.)
149171

150-
There are tests to this effect in src/test/run-pass:
151-
152-
traits-multidispatch-infer-convert-source-and-target.rs
153-
traits-multidispatch-infer-convert-target.rs
172+
**TODO**: The example says we can "select" the impl, but this section is talking specifically about candidate assembly. Does this mean we can sometimes skip confirmation? Or is this poor wording?
173+
**TODO**: Is the unification of `$Y` part of trait resolution or type inference? Or is this not the same type of "inference variable" as in type inference?
154174

155175
#### Winnowing: Resolving ambiguities
156176

@@ -167,94 +187,103 @@ impl<T:Copy> Get for T {
167187
}
168188

169189
impl<T:Get> Get for Box<T> {
170-
fn get(&self) -> Box<T> { box get_it(&**self) }
190+
fn get(&self) -> Box<T> { Box::new(get_it(&**self)) }
171191
}
172192
```
173193

174-
What happens when we invoke `get_it(&box 1_u16)`, for example? In this
194+
What happens when we invoke `get_it(&Box::new(1_u16))`, for example? In this
175195
case, the `Self` type is `Box<u16>` – that unifies with both impls,
176196
because the first applies to all types, and the second to all
177-
boxes. In the olden days we'd have called this ambiguous. But what we
178-
do now is do a second *winnowing* pass that considers where clauses
179-
and attempts to remove candidates – in this case, the first impl only
197+
boxes. In order for this to be unambiguous, the compiler does a *winnowing*
198+
pass that considers `where` clauses
199+
and attempts to remove candidates. In this case, the first impl only
180200
applies if `Box<u16> : Copy`, which doesn't hold. After winnowing,
181-
then, we are left with just one candidate, so we can proceed. There is
182-
a test of this in `src/test/run-pass/traits-conditional-dispatch.rs`.
183-
184-
#### Matching
185-
186-
The subroutines that decide whether a particular impl/where-clause/etc
187-
applies to a particular obligation. At the moment, this amounts to
188-
unifying the self types, but in the future we may also recursively
189-
consider some of the nested obligations, in the case of an impl.
201+
then, we are left with just one candidate, so we can proceed.
190202

191-
#### Lifetimes and selection
192-
193-
Because of how that lifetime inference works, it is not possible to
194-
give back immediate feedback as to whether a unification or subtype
195-
relationship between lifetimes holds or not. Therefore, lifetime
196-
matching is *not* considered during selection. This is reflected in
197-
the fact that subregion assignment is infallible. This may yield
198-
lifetime constraints that will later be found to be in error (in
199-
contrast, the non-lifetime-constraints have already been checked
200-
during selection and can never cause an error, though naturally they
201-
may lead to other errors downstream).
202-
203-
#### Where clauses
203+
#### `where` clauses
204204

205205
Besides an impl, the other major way to resolve an obligation is via a
206-
where clause. The selection process is always given a *parameter
207-
environment* which contains a list of where clauses, which are
208-
basically obligations that can assume are satisfiable. We will iterate
206+
where clause. The selection process is always given a [parameter
207+
environment] which contains a list of where clauses, which are
208+
basically obligations that we can assume are satisfiable. We will iterate
209209
over that list and check whether our current obligation can be found
210-
in that list, and if so it is considered satisfied. More precisely, we
210+
in that list. If so, it is considered satisfied. More precisely, we
211211
want to check whether there is a where-clause obligation that is for
212-
the same trait (or some subtrait) and for which the self types match,
213-
using the definition of *matching* given above.
212+
the same trait (or some subtrait) and which can match against the obligation.
213+
214+
[parameter environment]: ./param_env.html
214215

215216
Consider this simple example:
216217

217218
```rust
218-
trait A1 { /*...*/ }
219+
trait A1 {
220+
fn do_a1(&self);
221+
}
219222
trait A2 : A1 { /*...*/ }
220223

221-
trait B { /*...*/ }
224+
trait B {
225+
fn do_b(&self);
226+
}
222227

223-
fn foo<X:A2+B> { /*...*/ }
228+
fn foo<X:A2+B>(x: X) {
229+
x.do_a1(); // (*)
230+
x.do_b(); // (#)
231+
}
224232
```
225233

226-
Clearly we can use methods offered by `A1`, `A2`, or `B` within the
227-
body of `foo`. In each case, that will incur an obligation like `X :
228-
A1` or `X : A2`. The parameter environment will contain two
229-
where-clauses, `X : A2` and `X : B`. For each obligation, then, we
230-
search this list of where-clauses. To resolve an obligation `X:A1`,
231-
we would note that `X:A2` implies that `X:A1`.
234+
In the body of `foo`, clearly we can use methods of `A1`, `A2`, or `B`
235+
on variable `x`. The line marked `(*)` will incur an obligation `X: A1`,
236+
which the line marked `(#)` will incur an obligation `X: B`. Meanwhile,
237+
the parameter environment will contain two where-clauses: `X : A2` and `X : B`.
238+
For each obligation, then, we search this list of where-clauses. The
239+
obligation `X: B` trivially matches against the where-clause `X: B`.
240+
To resolve an obligation `X:A1`, we would note that `X:A2` implies that `X:A1`.
232241

233242
### Confirmation
234243

235-
Confirmation unifies the output type parameters of the trait with the
236-
values found in the obligation, possibly yielding a type error. If we
237-
return to our example of the `Convert` trait from the previous
238-
section, confirmation is where an error would be reported, because the
239-
impl specified that `T` would be `usize`, but the obligation reported
240-
`char`. Hence the result of selection would be an error.
244+
_Confirmation_ unifies the output type parameters of the trait with the
245+
values found in the obligation, possibly yielding a type error.
246+
247+
Suppose we have the following variation of the `Convert` example in the
248+
previous section:
249+
250+
```rust
251+
trait Convert<Target> {
252+
fn convert(&self) -> Target;
253+
}
254+
255+
impl Convert<usize> for isize { /*...*/ } // isize -> usize
256+
impl Convert<isize> for usize { /*...*/ } // usize -> isize
257+
258+
let x: isize = ...;
259+
let y: char = x.convert(); // NOTE: `y: char` now!
260+
```
261+
262+
Confirmation is where an error would be reported because the impl specified
263+
that `Target` would be `usize`, but the obligation reported `char`. Hence the
264+
result of selection would be an error.
265+
266+
Note that the candidate impl is chosen base on the `Self` type, but
267+
confirmation is done based on (in this case) the `Target` type parameter.
241268

242269
### Selection during translation
243270

244-
During type checking, we do not store the results of trait selection.
245-
We simply wish to verify that trait selection will succeed. Then
246-
later, at trans time, when we have all concrete types available, we
247-
can repeat the trait selection. In this case, we do not consider any
248-
where-clauses to be in scope. We know that therefore each resolution
249-
will resolve to a particular impl.
271+
As mentioned above, during type checking, we do not store the results of trait
272+
selection. At trans time, repeat the trait selection to choose a particular
273+
impl for each method call. In this second selection, we do not consider any
274+
where-clauses to be in scope because we know that each resolution will resolve
275+
to a particular impl.
250276

251277
One interesting twist has to do with nested obligations. In general, in trans,
252278
we only need to do a "shallow" selection for an obligation. That is, we wish to
253279
identify which impl applies, but we do not (yet) need to decide how to select
254-
any nested obligations. Nonetheless, we *do* currently do a complete resolution,
255-
and that is because it can sometimes inform the results of type inference. That is,
256-
we do not have the full substitutions in terms of the type variables of the impl available
257-
to us, so we must run trait selection to figure everything out.
280+
any nested obligations. Nonetheless, we *do* currently do a complete
281+
resolution, and that is because it can sometimes inform the results of type
282+
inference. That is, we do not have the full substitutions for the type
283+
variables of the impl available to us, so we must run trait selection to figure
284+
everything out.
285+
286+
**TODO**: is this still talking about trans?
258287

259288
Here is an example:
260289

@@ -272,214 +301,3 @@ nested obligation `isize : Bar<U>` to find out that `U=usize`.
272301

273302
It would be good to only do *just as much* nested resolution as
274303
necessary. Currently, though, we just do a full resolution.
275-
276-
# Higher-ranked trait bounds
277-
278-
One of the more subtle concepts at work are *higher-ranked trait
279-
bounds*. An example of such a bound is `for<'a> MyTrait<&'a isize>`.
280-
Let's walk through how selection on higher-ranked trait references
281-
works.
282-
283-
## Basic matching and skolemization leaks
284-
285-
Let's walk through the test `compile-fail/hrtb-just-for-static.rs` to see
286-
how it works. The test starts with the trait `Foo`:
287-
288-
```rust
289-
trait Foo<X> {
290-
fn foo(&self, x: X) { }
291-
}
292-
```
293-
294-
Let's say we have a function `want_hrtb` that wants a type which
295-
implements `Foo<&'a isize>` for any `'a`:
296-
297-
```rust
298-
fn want_hrtb<T>() where T : for<'a> Foo<&'a isize> { ... }
299-
```
300-
301-
Now we have a struct `AnyInt` that implements `Foo<&'a isize>` for any
302-
`'a`:
303-
304-
```rust
305-
struct AnyInt;
306-
impl<'a> Foo<&'a isize> for AnyInt { }
307-
```
308-
309-
And the question is, does `AnyInt : for<'a> Foo<&'a isize>`? We want the
310-
answer to be yes. The algorithm for figuring it out is closely related
311-
to the subtyping for higher-ranked types (which is described in
312-
`middle::infer::higher_ranked::doc`, but also in a [paper by SPJ] that
313-
I recommend you read).
314-
315-
1. Skolemize the obligation.
316-
2. Match the impl against the skolemized obligation.
317-
3. Check for skolemization leaks.
318-
319-
[paper by SPJ]: http://research.microsoft.com/en-us/um/people/simonpj/papers/higher-rank/
320-
321-
So let's work through our example. The first thing we would do is to
322-
skolemize the obligation, yielding `AnyInt : Foo<&'0 isize>` (here `'0`
323-
represents skolemized region #0). Note that now have no quantifiers;
324-
in terms of the compiler type, this changes from a `ty::PolyTraitRef`
325-
to a `TraitRef`. We would then create the `TraitRef` from the impl,
326-
using fresh variables for it's bound regions (and thus getting
327-
`Foo<&'$a isize>`, where `'$a` is the inference variable for `'a`). Next
328-
we relate the two trait refs, yielding a graph with the constraint
329-
that `'0 == '$a`. Finally, we check for skolemization "leaks" – a
330-
leak is basically any attempt to relate a skolemized region to another
331-
skolemized region, or to any region that pre-existed the impl match.
332-
The leak check is done by searching from the skolemized region to find
333-
the set of regions that it is related to in any way. This is called
334-
the "taint" set. To pass the check, that set must consist *solely* of
335-
itself and region variables from the impl. If the taint set includes
336-
any other region, then the match is a failure. In this case, the taint
337-
set for `'0` is `{'0, '$a}`, and hence the check will succeed.
338-
339-
Let's consider a failure case. Imagine we also have a struct
340-
341-
```rust
342-
struct StaticInt;
343-
impl Foo<&'static isize> for StaticInt;
344-
```
345-
346-
We want the obligation `StaticInt : for<'a> Foo<&'a isize>` to be
347-
considered unsatisfied. The check begins just as before. `'a` is
348-
skolemized to `'0` and the impl trait reference is instantiated to
349-
`Foo<&'static isize>`. When we relate those two, we get a constraint
350-
like `'static == '0`. This means that the taint set for `'0` is `{'0,
351-
'static}`, which fails the leak check.
352-
353-
## Higher-ranked trait obligations
354-
355-
Once the basic matching is done, we get to another interesting topic:
356-
how to deal with impl obligations. I'll work through a simple example
357-
here. Imagine we have the traits `Foo` and `Bar` and an associated impl:
358-
359-
```rust
360-
trait Foo<X> {
361-
fn foo(&self, x: X) { }
362-
}
363-
364-
trait Bar<X> {
365-
fn bar(&self, x: X) { }
366-
}
367-
368-
impl<X,F> Foo<X> for F
369-
where F : Bar<X>
370-
{
371-
}
372-
```
373-
374-
Now let's say we have a obligation `for<'a> Foo<&'a isize>` and we match
375-
this impl. What obligation is generated as a result? We want to get
376-
`for<'a> Bar<&'a isize>`, but how does that happen?
377-
378-
After the matching, we are in a position where we have a skolemized
379-
substitution like `X => &'0 isize`. If we apply this substitution to the
380-
impl obligations, we get `F : Bar<&'0 isize>`. Obviously this is not
381-
directly usable because the skolemized region `'0` cannot leak out of
382-
our computation.
383-
384-
What we do is to create an inverse mapping from the taint set of `'0`
385-
back to the original bound region (`'a`, here) that `'0` resulted
386-
from. (This is done in `higher_ranked::plug_leaks`). We know that the
387-
leak check passed, so this taint set consists solely of the skolemized
388-
region itself plus various intermediate region variables. We then walk
389-
the trait-reference and convert every region in that taint set back to
390-
a late-bound region, so in this case we'd wind up with `for<'a> F :
391-
Bar<&'a isize>`.
392-
393-
# Caching and subtle considerations therewith
394-
395-
In general we attempt to cache the results of trait selection. This
396-
is a somewhat complex process. Part of the reason for this is that we
397-
want to be able to cache results even when all the types in the trait
398-
reference are not fully known. In that case, it may happen that the
399-
trait selection process is also influencing type variables, so we have
400-
to be able to not only cache the *result* of the selection process,
401-
but *replay* its effects on the type variables.
402-
403-
## An example
404-
405-
The high-level idea of how the cache works is that we first replace
406-
all unbound inference variables with skolemized versions. Therefore,
407-
if we had a trait reference `usize : Foo<$1>`, where `$n` is an unbound
408-
inference variable, we might replace it with `usize : Foo<%0>`, where
409-
`%n` is a skolemized type. We would then look this up in the cache.
410-
If we found a hit, the hit would tell us the immediate next step to
411-
take in the selection process: i.e. apply impl #22, or apply where
412-
clause `X : Foo<Y>`. Let's say in this case there is no hit.
413-
Therefore, we search through impls and where clauses and so forth, and
414-
we come to the conclusion that the only possible impl is this one,
415-
with def-id 22:
416-
417-
```rust
418-
impl Foo<isize> for usize { ... } // Impl #22
419-
```
420-
421-
We would then record in the cache `usize : Foo<%0> ==>
422-
ImplCandidate(22)`. Next we would confirm `ImplCandidate(22)`, which
423-
would (as a side-effect) unify `$1` with `isize`.
424-
425-
Now, at some later time, we might come along and see a `usize :
426-
Foo<$3>`. When skolemized, this would yield `usize : Foo<%0>`, just as
427-
before, and hence the cache lookup would succeed, yielding
428-
`ImplCandidate(22)`. We would confirm `ImplCandidate(22)` which would
429-
(as a side-effect) unify `$3` with `isize`.
430-
431-
## Where clauses and the local vs global cache
432-
433-
One subtle interaction is that the results of trait lookup will vary
434-
depending on what where clauses are in scope. Therefore, we actually
435-
have *two* caches, a local and a global cache. The local cache is
436-
attached to the `ParamEnv` and the global cache attached to the
437-
`tcx`. We use the local cache whenever the result might depend on the
438-
where clauses that are in scope. The determination of which cache to
439-
use is done by the method `pick_candidate_cache` in `select.rs`. At
440-
the moment, we use a very simple, conservative rule: if there are any
441-
where-clauses in scope, then we use the local cache. We used to try
442-
and draw finer-grained distinctions, but that led to a serious of
443-
annoying and weird bugs like #22019 and #18290. This simple rule seems
444-
to be pretty clearly safe and also still retains a very high hit rate
445-
(~95% when compiling rustc).
446-
447-
# Specialization
448-
449-
Defined in the `specialize` module.
450-
451-
The basic strategy is to build up a *specialization graph* during
452-
coherence checking. Insertion into the graph locates the right place
453-
to put an impl in the specialization hierarchy; if there is no right
454-
place (due to partial overlap but no containment), you get an overlap
455-
error. Specialization is consulted when selecting an impl (of course),
456-
and the graph is consulted when propagating defaults down the
457-
specialization hierarchy.
458-
459-
You might expect that the specialization graph would be used during
460-
selection – i.e. when actually performing specialization. This is
461-
not done for two reasons:
462-
463-
- It's merely an optimization: given a set of candidates that apply,
464-
we can determine the most specialized one by comparing them directly
465-
for specialization, rather than consulting the graph. Given that we
466-
also cache the results of selection, the benefit of this
467-
optimization is questionable.
468-
469-
- To build the specialization graph in the first place, we need to use
470-
selection (because we need to determine whether one impl specializes
471-
another). Dealing with this reentrancy would require some additional
472-
mode switch for selection. Given that there seems to be no strong
473-
reason to use the graph anyway, we stick with a simpler approach in
474-
selection, and use the graph only for propagating default
475-
implementations.
476-
477-
Trait impl selection can succeed even when multiple impls can apply,
478-
as long as they are part of the same specialization family. In that
479-
case, it returns a *single* impl on success – this is the most
480-
specialized impl *known* to apply. However, if there are any inference
481-
variables in play, the returned impl may not be the actual impl we
482-
will use at trans time. Thus, we take special care to avoid projecting
483-
associated types unless either (1) the associated type does not use
484-
`default` and thus cannot be overridden or (2) all input types are
485-
known concretely.

‎src/trait-specialization.md

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Specialization
2+
3+
Defined in the `specialize` module.
4+
5+
The basic strategy is to build up a *specialization graph* during
6+
coherence checking. Insertion into the graph locates the right place
7+
to put an impl in the specialization hierarchy; if there is no right
8+
place (due to partial overlap but no containment), you get an overlap
9+
error. Specialization is consulted when selecting an impl (of course),
10+
and the graph is consulted when propagating defaults down the
11+
specialization hierarchy.
12+
13+
You might expect that the specialization graph would be used during
14+
selection – i.e. when actually performing specialization. This is
15+
not done for two reasons:
16+
17+
- It's merely an optimization: given a set of candidates that apply,
18+
we can determine the most specialized one by comparing them directly
19+
for specialization, rather than consulting the graph. Given that we
20+
also cache the results of selection, the benefit of this
21+
optimization is questionable.
22+
23+
- To build the specialization graph in the first place, we need to use
24+
selection (because we need to determine whether one impl specializes
25+
another). Dealing with this reentrancy would require some additional
26+
mode switch for selection. Given that there seems to be no strong
27+
reason to use the graph anyway, we stick with a simpler approach in
28+
selection, and use the graph only for propagating default
29+
implementations.
30+
31+
Trait impl selection can succeed even when multiple impls can apply,
32+
as long as they are part of the same specialization family. In that
33+
case, it returns a *single* impl on success – this is the most
34+
specialized impl *known* to apply. However, if there are any inference
35+
variables in play, the returned impl may not be the actual impl we
36+
will use at trans time. Thus, we take special care to avoid projecting
37+
associated types unless either (1) the associated type does not use
38+
`default` and thus cannot be overridden or (2) all input types are
39+
known concretely.

0 commit comments

Comments
 (0)
Please sign in to comment.