Skip to content

Commit 3ffb774

Browse files
committed
Add back the canonicalization chapter.
1 parent 4d43a3b commit 3ffb774

File tree

2 files changed

+257
-0
lines changed

2 files changed

+257
-0
lines changed

src/SUMMARY.md

+1
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@
123123
- [Lowering to logic](./traits/lowering-to-logic.md)
124124
- [Goals and clauses](./traits/goals-and-clauses.md)
125125
- [Canonical queries](./traits/canonical-queries.md)
126+
- [Canonicalization](./traits/canonicalization.md)
126127
- [Next-gen trait solving](./solve/trait-solving.md)
127128
- [Invariants of the type system](./solve/invariants.md)
128129
- [The solver](./solve/the-solver.md)

src/traits/canonicalization.md

+256
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
# Canonicalization
2+
3+
Canonicalization is the process of **isolating** an inference value
4+
from its context. It is a key part of implementing
5+
[canonical queries][cq], and you may wish to read the parent chapter
6+
to get more context.
7+
8+
Canonicalization is really based on a very simple concept: every
9+
[inference variable](../type-inference.html#vars) is always in one of
10+
two states: either it is **unbound**, in which case we don't know yet
11+
what type it is, or it is **bound**, in which case we do. So to
12+
isolate some data-structure T that contains types/regions from its
13+
environment, we just walk down and find the unbound variables that
14+
appear in T; those variables get replaced with "canonical variables",
15+
starting from zero and numbered in a fixed order (left to right, for
16+
the most part, but really it doesn't matter as long as it is
17+
consistent).
18+
19+
[cq]: ./canonical-queries.html
20+
21+
So, for example, if we have the type `X = (?T, ?U)`, where `?T` and
22+
`?U` are distinct, unbound inference variables, then the canonical
23+
form of `X` would be `(?0, ?1)`, where `?0` and `?1` represent these
24+
**canonical placeholders**. Note that the type `Y = (?U, ?T)` also
25+
canonicalizes to `(?0, ?1)`. But the type `Z = (?T, ?T)` would
26+
canonicalize to `(?0, ?0)` (as would `(?U, ?U)`). In other words, the
27+
exact identity of the inference variables is not important – unless
28+
they are repeated.
29+
30+
We use this to improve caching as well as to detect cycles and other
31+
things during trait resolution. Roughly speaking, the idea is that if
32+
two trait queries have the same canonical form, then they will get
33+
the same answer. That answer will be expressed in terms of the
34+
canonical variables (`?0`, `?1`), which we can then map back to the
35+
original variables (`?T`, `?U`).
36+
37+
## Canonicalizing the query
38+
39+
To see how it works, imagine that we are asking to solve the following
40+
trait query: `?A: Foo<'static, ?B>`, where `?A` and `?B` are unbound.
41+
This query contains two unbound variables, but it also contains the
42+
lifetime `'static`. The trait system generally ignores all lifetimes
43+
and treats them equally, so when canonicalizing, we will *also*
44+
replace any [free lifetime](../appendix/background.html#free-vs-bound) with a
45+
canonical variable (Note that `'static` is actually a _free_ lifetime
46+
variable here. We are not considering it in the typing context of the whole
47+
program but only in the context of this trait reference. Mathematically, we
48+
are not quantifying over the whole program, but only this obligation).
49+
Therefore, we get the following result:
50+
51+
```text
52+
?0: Foo<'?1, ?2>
53+
```
54+
55+
Sometimes we write this differently, like so:
56+
57+
```text
58+
for<T,L,T> { ?0: Foo<'?1, ?2> }
59+
```
60+
61+
This `for<>` gives some information about each of the canonical
62+
variables within. In this case, each `T` indicates a type variable,
63+
so `?0` and `?2` are types; the `L` indicates a lifetime variable, so
64+
`?1` is a lifetime. The `canonicalize` method *also* gives back a
65+
`CanonicalVarValues` array OV with the "original values" for each
66+
canonicalized variable:
67+
68+
```text
69+
[?A, 'static, ?B]
70+
```
71+
72+
We'll need this vector OV later, when we process the query response.
73+
74+
## Executing the query
75+
76+
Once we've constructed the canonical query, we can try to solve it.
77+
To do so, we will wind up creating a fresh inference context and
78+
**instantiating** the canonical query in that context. The idea is that
79+
we create a substitution S from the canonical form containing a fresh
80+
inference variable (of suitable kind) for each canonical variable.
81+
So, for our example query:
82+
83+
```text
84+
for<T,L,T> { ?0: Foo<'?1, ?2> }
85+
```
86+
87+
the substitution S might be:
88+
89+
```text
90+
S = [?A, '?B, ?C]
91+
```
92+
93+
We can then replace the bound canonical variables (`?0`, etc) with
94+
these inference variables, yielding the following fully instantiated
95+
query:
96+
97+
```text
98+
?A: Foo<'?B, ?C>
99+
```
100+
101+
Remember that substitution S though! We're going to need it later.
102+
103+
OK, now that we have a fresh inference context and an instantiated
104+
query, we can go ahead and try to solve it. The trait solver itself is
105+
explained in more detail in [another section](./slg.html), but
106+
suffice to say that it will compute a [certainty value][cqqr] (`Proven` or
107+
`Ambiguous`) and have side-effects on the inference variables we've
108+
created. For example, if there were only one impl of `Foo`, like so:
109+
110+
[cqqr]: ./canonical-queries.html#query-response
111+
112+
```rust,ignore
113+
impl<'a, X> Foo<'a, X> for Vec<X>
114+
where X: 'a
115+
{ ... }
116+
```
117+
118+
then we might wind up with a certainty value of `Proven`, as well as
119+
creating fresh inference variables `'?D` and `?E` (to represent the
120+
parameters on the impl) and unifying as follows:
121+
122+
- `'?B = '?D`
123+
- `?A = Vec<?E>`
124+
- `?C = ?E`
125+
126+
We would also accumulate the region constraint `?E: '?D`, due to the
127+
where clause.
128+
129+
In order to create our final query result, we have to "lift" these
130+
values out of the query's inference context and into something that
131+
can be reapplied in our original inference context. We do that by
132+
**re-applying canonicalization**, but to the **query result**.
133+
134+
## Canonicalizing the query result
135+
136+
As discussed in [the parent section][cqqr], most trait queries wind up
137+
with a result that brings together a "certainty value" `certainty`, a
138+
result substitution `var_values`, and some region constraints. To
139+
create this, we wind up re-using the substitution S that we created
140+
when first instantiating our query. To refresh your memory, we had a query
141+
142+
```text
143+
for<T,L,T> { ?0: Foo<'?1, ?2> }
144+
```
145+
146+
for which we made a substutition S:
147+
148+
```text
149+
S = [?A, '?B, ?C]
150+
```
151+
152+
We then did some work which unified some of those variables with other things.
153+
If we "refresh" S with the latest results, we get:
154+
155+
```text
156+
S = [Vec<?E>, '?D, ?E]
157+
```
158+
159+
These are precisely the new values for the three input variables from
160+
our original query. Note though that they include some new variables
161+
(like `?E`). We can make those go away by canonicalizing again! We don't
162+
just canonicalize S, though, we canonicalize the whole query response QR:
163+
164+
```text
165+
QR = {
166+
certainty: Proven, // or whatever
167+
var_values: [Vec<?E>, '?D, ?E] // this is S
168+
region_constraints: [?E: '?D], // from the impl
169+
value: (), // for our purposes, just (), but
170+
// in some cases this might have
171+
// a type or other info
172+
}
173+
```
174+
175+
The result would be as follows:
176+
177+
```text
178+
Canonical(QR) = for<T, L> {
179+
certainty: Proven,
180+
var_values: [Vec<?0>, '?1, ?0]
181+
region_constraints: [?0: '?1],
182+
value: (),
183+
}
184+
```
185+
186+
(One subtle point: when we canonicalize the query **result**, we do not
187+
use any special treatment for free lifetimes. Note that both
188+
references to `'?D`, for example, were converted into the same
189+
canonical variable (`?1`). This is in contrast to the original query,
190+
where we canonicalized every free lifetime into a fresh canonical
191+
variable.)
192+
193+
Now, this result must be reapplied in each context where needed.
194+
195+
## Processing the canonicalized query result
196+
197+
In the previous section we produced a canonical query result. We now have
198+
to apply that result in our original context. If you recall, way back in the
199+
beginning, we were trying to prove this query:
200+
201+
```text
202+
?A: Foo<'static, ?B>
203+
```
204+
205+
We canonicalized that into this:
206+
207+
```text
208+
for<T,L,T> { ?0: Foo<'?1, ?2> }
209+
```
210+
211+
and now we got back a canonical response:
212+
213+
```text
214+
for<T, L> {
215+
certainty: Proven,
216+
var_values: [Vec<?0>, '?1, ?0]
217+
region_constraints: [?0: '?1],
218+
value: (),
219+
}
220+
```
221+
222+
We now want to apply that response to our context. Conceptually, how
223+
we do that is to (a) instantiate each of the canonical variables in
224+
the result with a fresh inference variable, (b) unify the values in
225+
the result with the original values, and then (c) record the region
226+
constraints for later. Doing step (a) would yield a result of
227+
228+
```text
229+
{
230+
certainty: Proven,
231+
var_values: [Vec<?C>, '?D, ?C]
232+
^^ ^^^ fresh inference variables
233+
region_constraints: [?C: '?D],
234+
value: (),
235+
}
236+
```
237+
238+
Step (b) would then unify:
239+
240+
```text
241+
?A with Vec<?C>
242+
'static with '?D
243+
?B with ?C
244+
```
245+
246+
And finally the region constraint of `?C: 'static` would be recorded
247+
for later verification.
248+
249+
(What we *actually* do is a mildly optimized variant of that: Rather
250+
than eagerly instantiating all of the canonical values in the result
251+
with variables, we instead walk the vector of values, looking for
252+
cases where the value is just a canonical variable. In our example,
253+
`values[2]` is `?C`, so that means we can deduce that `?C := ?B` and
254+
`'?D := 'static`. This gives us a partial set of values. Anything for
255+
which we do not find a value, we create an inference variable.)
256+

0 commit comments

Comments
 (0)