Skip to content

Commit 5fe3bc9

Browse files
committed
work on traits chapters
1 parent 8a005f5 commit 5fe3bc9

12 files changed

+912
-3
lines changed

Diff for: src/SUMMARY.md

+11-2
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,19 @@
1717
- [The HIR (High-level IR)](./hir.md)
1818
- [The `ty` module: representing types](./ty.md)
1919
- [Type inference](./type-inference.md)
20-
- [Trait resolution](./trait-resolution.md)
20+
- [Trait solving (old-style)](./trait-resolution.md)
2121
- [Higher-ranked trait bounds](./trait-hrtb.md)
2222
- [Caching subtleties](./trait-caching.md)
23-
- [Speciailization](./trait-specialization.md)
23+
- [Specialization](./trait-specialization.md)
24+
- [Trait solving (new-style)](./traits.md)
25+
- [Canonicalization](./traits-canonicalization.md)
26+
- [Lowering to logic](./traits-lowering-to-logic.md)
27+
- [Goals and clauses](./traits-goals-and-clauses.md)
28+
- [Lowering rules](./traits-lowering-rules.md)
29+
- [Equality and associated types](./traits-associated-types.md)
30+
- [Region constraints](./traits-regions.md)
31+
- [Well-formedness checking](./traits-wf.md)
32+
- [Bibliography](./traits-bibliography.md)
2433
- [Type checking](./type-checking.md)
2534
- [The MIR (Mid-level IR)](./mir.md)
2635
- [MIR construction](./mir-construction.md)

Diff for: src/trait-resolution.md

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1-
# Trait resolution
1+
# Trait resolution (old-style)
22

33
This chapter describes the general process of _trait resolution_ and points out
44
some non-obvious things.
55

6+
**Note:** This chapter (and its subchapters) describe how the trait
7+
solver **currently** works. However, we are in the process of
8+
designing a new trait solver. If you'd prefer to read about *that*,
9+
see [the traits chapter](./traits.html).
10+
611
## Major concepts
712

813
Trait resolution is the process of pairing up an impl with each

Diff for: src/traits-associated-types.md

+143
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
# Equality and associated types
2+
3+
This section covers how the trait system handles equality between
4+
associated types. The full system consists of several moving parts,
5+
which we will introduce one by one:
6+
7+
- Projection and the `Normalize` predicate
8+
- Skolemization
9+
- The `ProjectionEq` predicate
10+
- Integration with unification
11+
12+
## Associated type projection and normalization
13+
14+
When a trait defines an associated type (e.g.,
15+
[the `Item` type in the `IntoIterator` trait][intoiter-item]), that
16+
type can be referenced by the user using an **associated type
17+
projection** like `<Option<u32> as IntoIterator>::Item`. (Often,
18+
though, people will use the shorthand syntax `T::Item` -- presently,
19+
that syntax is expanded during
20+
["type collection"](./type-checking.html) into the explicit form,
21+
though that is something we may want to change in the future.)
22+
23+
In some cases, associated type projections can be **normalized** --
24+
that is, simplified -- based on the types given in an impl. So, to
25+
continue with our example, the impl of `IntoIterator` for `Option<T>`
26+
declares (among other things) that `Item = T`:
27+
28+
```rust
29+
impl<T> IntoIterator for Option<T> {
30+
type Item = T;
31+
..
32+
}
33+
```
34+
35+
This means we can normalize the projection `<Option<u32> as
36+
IntoIterator>::Item` to just `u32`.
37+
38+
In this case, the projection was a "monomorphic" one -- that is, it
39+
did not have any type parameters. Monomorphic projections are special
40+
because they can **always** be fully normalized -- but often we can
41+
normalize other associated type projections as well. For example,
42+
`<Option<?T> as IntoIterator>::Item` (where `?T` is an inference
43+
variable) can be normalized to just `?T`.
44+
45+
In our logic, normalization is defined by a predicate
46+
`Normalize`. The `Normalize` clauses arise only from
47+
impls. For example, the `impl` of `IntoIterator` for `Option<T>` that
48+
we saw above would be lowered to a program clause like so:
49+
50+
forall<T> {
51+
Normalize(<Option<T> as IntoIterator>::Item -> T)
52+
}
53+
54+
(An aside: since we do not permit quantification over traits, this is
55+
really more like a family of predicates, one for each associated
56+
type.)
57+
58+
We could apply that rule to normalize either of the examples that
59+
we've seen so far.
60+
61+
## Skolemized associated types
62+
63+
Sometimes however we want to work with associated types that cannot be
64+
normalized. For example, consider this function:
65+
66+
```rust
67+
fn foo<T: IntoIterator>(...) { ... }
68+
```
69+
70+
In this context, how would we normalize the type `T::Item`? Without
71+
knowing what `T` is, we can't really do so. To represent this case, we
72+
introduce a type called a **skolemized associated type
73+
projection**. This is written like so `(IntoIterator::Item)<T>`. You
74+
may note that it looks a lot like a regular type (e.g., `Option<T>`),
75+
except that the "name" of the type is `(IntoIterator::Item)`. This is
76+
not an accident: skolemized associated type projections work just like
77+
ordinary types like `Vec<T>` when it comes to unification. That is,
78+
they are only considered equal if (a) they are both references to the
79+
same associated type, like `IntoIterator::Item` and (b) their type
80+
arguments are equal.
81+
82+
Skolemized associated types are never written directly by the user.
83+
They are used internally by the trait system only, as we will see
84+
shortly.
85+
86+
## Projection equality
87+
88+
So far we have seen two ways to answer the question of "When can we
89+
consider an associated type projection equal to another type?":
90+
91+
- the `Normalize` predicate could be used to transform associated type
92+
projections when we knew which impl was applicable;
93+
- **skolemized** associated types can be used when we don't.
94+
95+
We now introduce the `ProjectionEq` predicate to bring those two cases
96+
together. The `ProjectionEq` predicate looks like so:
97+
98+
ProjectionEq(<T as IntoIterator>::Item = U)
99+
100+
and we will see that it can be proven *either* via normalization or
101+
skolemization. As part of lowering an associated type declaration from
102+
some trait, we create two program clauses for `ProjectionEq`:
103+
104+
forall<T, U> {
105+
ProjectionEq(<T as IntoIterator>::Item = U) :-
106+
Normalize(<T as IntoIterator>::Item -> U)
107+
}
108+
109+
forall<T> {
110+
ProjectionEq(<T as IntoIterator>::Item = (IntoIterator::Item)<T>)
111+
}
112+
113+
These are the only two `ProjectionEq` program clauses we ever make for
114+
any given associated item.
115+
116+
## Integration with unification
117+
118+
Now we are ready to discuss how associated type equality integrates
119+
with unification. As described in the
120+
[type inference](./type-inference.html) section, unification is
121+
basically a procedure with a signature like this:
122+
123+
Unify(A, B) = Result<(Subgoals, RegionConstraints), NoSolution>
124+
125+
In other words, we try to unify two things A and B. That procedure
126+
might just fail, in which case we get back `Err(NoSolution)`. This
127+
would happen, for example, if we tried to unify `u32` and `i32`.
128+
129+
The key point is that, on success, unification can also give back to
130+
us a set of subgoals that still remain to be proven (it can also give
131+
back region constraints, but those are not relevant here).
132+
133+
Whenever unification encounters an (unskolemized!) associated type
134+
projection P being equated with some other type T, it always succeeds,
135+
but it produces a subgoal `ProjectionEq(P = T)` that is propagated
136+
back up. Thus it falls to the ordinary workings of the trait system
137+
to process that constraint.
138+
139+
(If we unify two projections P1 and P2, then unification produces a
140+
variable X and asks us to prove that `ProjectionEq(P1 = X)` and
141+
`ProjectionEq(P2 = X)`. That used to be needed in an older system to
142+
prevent cycles; I rather doubt it still is. -nmatsakis)
143+

Diff for: src/traits-bibliography.md

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Bibliography
2+
3+
If you'd like to read more background material, here are some
4+
recommended texts and papers:
5+
6+
[Programming with Higher-order Logic][phl], by Dale Miller and Gopalan
7+
Nadathur, covers the key concepts of Lambda prolog. Although it's a
8+
slim little volume, it's the kind of book where you learn something
9+
new every time you open it.
10+
11+
[phl]: https://www.amazon.com/Programming-Higher-Order-Logic-Dale-Miller/dp/052187940X
12+
13+
<a name=pphhf>
14+
15+
["A proof procedure for the logic of Hereditary Harrop formulas"][pphhf],
16+
by Gopalan Nadathur. This paper covers the basics of universes,
17+
environments, and Lambda Prolog-style proof search. Quite readable.
18+
19+
[pphhf]: https://dl.acm.org/citation.cfm?id=868380
20+
21+
<a name=slg>
22+
23+
["A new formulation of tabled resolution with delay"][nftrd], by
24+
[Theresa Swift]. This paper gives a kind of abstract treatment of the
25+
SLG formulation that is the basis for our on-demand solver.
26+
27+
[nftrd]: https://dl.acm.org/citation.cfm?id=651202
28+
[ts]: http://www3.cs.stonybrook.edu/~tswift/

Diff for: src/traits-canonicalization.md

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# Canonicalization
2+
3+
Canonicalization is the process of **isolating** an inference value
4+
from its context. It is really based on a very simple concept: every
5+
[inference variable](./type-inference.html#vars) is always in one of two
6+
states: either it is **unbound**, in which case we don't know yet what
7+
type it is, or it is **bound**, in which case we do. So to isolate
8+
some thing T from its environment, we just walk down and find the
9+
unbound variables that appear in T; those variables get renumbered in
10+
a canonical order (left to right, for the most part, but really it
11+
doesn't matter as long as it is consistent).
12+
13+
So, for example, if we have the type `X = (?T, ?U)`, where `?T` and
14+
`?U` are distinct, unbound inference variables, then the canonical
15+
form of `X` would be `(?0, ?1)`, where `?0` and `?1` represent these
16+
**canonical placeholders**. Note that the type `Y = (?U, ?T)` also
17+
canonicalizes to `(?0, ?1)`. But the type `Z = (?T, ?T)` would
18+
canonicalize to `(?0, ?0)` (as would `(?U, ?U)`). In other words, the
19+
exact identity of the inference variables is not important -- unless
20+
they are repeated.
21+
22+
We use this to improve caching as well as to detect cycles and other
23+
things during trait resolution. Roughly speaking, the idea is that if
24+
two trait queries have the same canonicalize form, then they will get
25+
the same answer -- modulo the precise identities of the variables
26+
involved.
27+
28+
To see how it works, imagine that we are asking to solve the following
29+
trait query: `?A: Foo<'static, ?B>`, where `?A` and `?B` are unbound.
30+
This query contains two unbound variables, but it also contains the
31+
lifetime `'static`. The trait system generally ignores all lifetimes
32+
and treats them equally, so when canonicalizing, we will *also*
33+
replace any [free lifetime](./background.html#free-vs-bound) with a
34+
canonical variable. Therefore, we get the following result:
35+
36+
?0: Foo<'?1, ?2>
37+
38+
Sometimes we write this differently, like so:
39+
40+
for<T,L,T> { ?0: Foo<'?1, ?2> }
41+
42+
This `for<>` gives some information about each of the canonical
43+
variables within. In this case, I am saying that `?0` is a type
44+
(`T`), `?1` is a lifetime (`L`), and `?2` is also a type (`T`). The
45+
`canonicalize` method *also* gives back a `CanonicalVarValues` array
46+
with the "original values" for each canonicalized variable:
47+
48+
[?A, 'static, ?B]
49+
50+
Now we do the query and get back some result R. As part of that
51+
result, we'll have an array of values for the canonical inputs. For
52+
example, the canonical result might be:
53+
54+
```
55+
for<2> {
56+
values = [ Vec<?0>, '1, ?0 ]
57+
^^ ^^ ^^ these are variables in the result!
58+
...
59+
}
60+
```
61+
62+
Note that this result is itself canonical and may include some
63+
variables (in this case, `?0`).
64+
65+
What we want to do conceptually is to (a) instantiate each of the
66+
canonical variables in the result with a fresh inference variable
67+
and then (b) unify the values in the result with the original values.
68+
Doing step (a) would yield a result of
69+
70+
```
71+
{
72+
values = [ Vec<?C>, '?X, ?C ]
73+
^^ ^^^ fresh inference variables in `self`
74+
..
75+
}
76+
```
77+
78+
Step (b) would then unify:
79+
80+
```
81+
?A with Vec<?C>
82+
'static with '?X
83+
?B with ?C
84+
```
85+
86+
(What we actually do is a mildly optimized variant of that: Rather
87+
than eagerly instantiating all of the canonical values in the result
88+
with variables, we instead walk the vector of values, looking for
89+
cases where the value is just a canonical variable. In our example,
90+
`values[2]` is `?C`, so that we means we can deduce that `?C := ?B and
91+
`'?X := 'static`. This gives us a partial set of values. Anything for
92+
which we do not find a value, we create an inference variable.)
93+

0 commit comments

Comments
 (0)