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 901e533

Browse files
committedMar 7, 2018
work on traits chapters
1 parent 8a005f5 commit 901e533

11 files changed

+900
-3
lines changed
 

‎src/SUMMARY.md

+10-2
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,18 @@
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 bsolving (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+
- [Bibliography](./traits-bibliography.md)
2432
- [Type checking](./type-checking.md)
2533
- [The MIR (Mid-level IR)](./mir.md)
2634
- [MIR construction](./mir-construction.md)

‎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

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

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

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

‎src/traits-goals-and-clauses.md

+148
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
# Goals and clauses
2+
3+
In logic programming terms, a **goal** is something that you must
4+
prove and a **clause** is something that you know is true. As
5+
described in the [lowering to logic](./traits-lowering-to-logic.html)
6+
chapter, Rust's trait solver is based on an extension of hereditary
7+
harrop (HH) clauses, which extend traditional Prolog Horn clauses with
8+
a few new superpowers.
9+
10+
## Goals and clauses meta structure
11+
12+
In Rust's solver, **goals** and **clauses** have the following forms
13+
(note that the two definitions reference one another):
14+
15+
Goal = DomainGoal
16+
| Goal && Goal
17+
| Goal || Goal
18+
| exists<K> { Goal } // existential quantification
19+
| forall<K> { Goal } // universal quantification
20+
| if (Clause) { Goal } // implication
21+
22+
Clause = DomainGoal
23+
| Clause :- Goal // if can prove Goal, then Clause is true
24+
| Clause && Clause
25+
| forall<K> { Clause }
26+
27+
K = <type> // a "kind"
28+
| <lifetime>
29+
30+
The proof procedure for these sorts of goals is actually quite
31+
straightforward. Essentially, it's a form of depth-first search. The
32+
paper
33+
["A Proof Procedure for the Logic of Hereditary Harrop Formulas"][pphhf]
34+
gives the details.
35+
36+
[pphhf]: ./traits-bibliography.html#pphhf
37+
38+
<a name="domain-goals">
39+
40+
## Domain goals
41+
42+
To define the set of *domain goals* in our system, we need to first
43+
introduce a few simple formulations. A **trait reference** consists of
44+
the name of a trait allow with a suitable set of inputs P0..Pn:
45+
46+
TraitRef = P0: TraitName<P1..Pn>
47+
48+
So, for example, `u32: Display` is a trait reference, as is `Vec<T>:
49+
IntoIterator`. Note that Rust surface syntax also permits some extra
50+
things, like associated type bindings (`Vec<T>: IntoIterator<Item =
51+
T>`), that are not part of a trait reference.
52+
53+
A **projection** consists of a an associated item reference along with
54+
its inputs P0..Pm:
55+
56+
Projection = <P0 as TraitName<P1..Pn>>::AssocItem<Pn+1..Pm>
57+
58+
Given that, we can define a `DomainGoal` as follows:
59+
60+
DomainGoal = Implemented(TraitRef)
61+
| ProjectionEq(Projection = Type)
62+
| Normalize(Projection -> Type)
63+
| FromEnv(TraitRef)
64+
| FromEnv(Projection = Type)
65+
| WellFormed(Type)
66+
| WellFormed(TraitRef)
67+
| WellFormed(Projection = Type)
68+
| Outlives(Type, Region)
69+
| Outlives(Region, Region)
70+
71+
- `Implemented(TraitRef)` -- true if the given trait is
72+
implemented for the given input types and lifetimes
73+
- `FromEnv(TraitEnv)` -- true if the given trait is *assumed* to be implemented;
74+
that is, if it can be derived from the in-scope where clauses
75+
- as we'll see in the section on lowering, `FromEnv(X)` implies
76+
`Implemented(X)` but not vice versa. This distinction is crucial
77+
to [implied bounds].
78+
- `ProjectionEq(Projection = Type)` -- the given associated type `Projection` is equal
79+
to `Type`; see [the section on associated types](./traits-associated-types.html)
80+
- `Normalize(Projection -> Type)` -- the given associated type `Projection` can be normalized
81+
to `Type`
82+
- as discussed in [the section on associated types](./traits-associated-types.html),
83+
`Normalize` implies `ProjectionEq` but not vice versa
84+
- `WellFormed(..)` -- these goals imply that the given item is
85+
*well-formed*
86+
- well-formedness is important to [implied bounds].
87+
88+
<a name=coinductive>
89+
90+
## Coinductive goals
91+
92+
Most goals in our system are "inductive". In an inductive goal,
93+
circular reasoning is disallowed. Consider this example clause:
94+
95+
Implemented(Foo: Bar) :-
96+
Implemented(Foo: Bar).
97+
98+
Considered inductively, this clause is useless: if we are trying to
99+
prove `Implemented(Foo: Bar)`, we would then recursively have to prove
100+
`Implemented(Foo: Bar)`, and that cycle would continue ad infinitum
101+
(the trait solver will terminate here, it would just consider that
102+
`Implemented(Foo: Bar)` is not known to be true).
103+
104+
However, some goals are *co-inductive*. Simply put, this means that
105+
cycles are OK. So, if `Bar` were a co-inductive trait, then the rule
106+
above would be perfectly valid, and it would indicate that
107+
`Implemented(Foo: Bar)` is true.
108+
109+
*Auto traits* are one example in Rust where co-inductive goals are used.
110+
Consider the `Send` trait, and imagine that we have this struct:
111+
112+
```rust
113+
struct Foo {
114+
next: Option<Box<Foo>>
115+
}
116+
```
117+
118+
The default rules for auto traits say that `Foo` is `Send` if the
119+
types of its fields are `Send`. Therefore, we would have a rule like
120+
121+
Implemented(Foo: Send) :-
122+
Implemented(Option<Box<Foo>>: Send).
123+
124+
As you can probably imagine, proving that `Option<Box<Foo>>: Send` is
125+
going to wind up circularly requiring us to prove that `Foo: Send`
126+
again. So this would be an example where we wind up in a cycle -- but
127+
that's ok, we *do* consider `Foo: Send` to hold, even though it
128+
references itself.
129+
130+
In general, co-inductive traits are used in Rust trait solving when we
131+
want to enumerate a fixed set of possibilities. In the case of auto
132+
traits, we are enumerating the set of reachable types from a given
133+
starting point (i.e., `Foo` can reach values of type
134+
`Option<Box<Foo>>`, which implies it can reach values of type
135+
`Box<Foo>`, and then of type `Foo`, and then the cycle is complete).
136+
137+
In addition to auto traits, `WellFormed` predicates are co-inductive.
138+
These are used to achieve a similar "enumerate all the cases" pattern,
139+
as described in the section on [implied bounds].
140+
141+
[implied bounds]: ./traits-lowering-rules.html#implied-bounds
142+
143+
## Incomplete chapter
144+
145+
Some topics yet to be written:
146+
147+
- Elaborate on the proof procedure
148+
- SLG solving -- introduce negative reasoning

‎src/traits-lowering-rules.md

+267
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
# Lowering rules
2+
3+
This section gives the complete lowering rules for Rust traits into
4+
[program clauses][pc]. These rules reference the [domain goals][dg] defined in
5+
an earlier section.
6+
7+
[pc]: ./traits-goals-and-clauses.html
8+
[dg]: ./traits-goals-and-clauses.html#domain-goals
9+
10+
## Notation
11+
12+
The nonterminal `Pi` is used to mean some generic *parameter*, either a
13+
named lifetime like `'a` or a type paramter like `A`.
14+
15+
The nonterminal `Ai` is used to mean some generic *argument*, which
16+
might be a lifetime like `'a` or a type like `Vec<A>`.
17+
18+
When defining the lowering rules, we will give goals and clauses in
19+
the [notation given in this section](./traits-goals-and-clauses.html).
20+
We sometimes insert "macros" like `LowerWhereClause!` into these
21+
definitions; these macros reference other sections within this chapter.
22+
23+
## Lowering where clauses
24+
25+
When used in a goal position, where clauses can be mapped directly
26+
[domain goals][dg], as follows:
27+
28+
- `A0: Foo<A1..An>` maps to `Implemented(A0: Foo<A1..An>)`.
29+
- `A0: Foo<A1..An, Item = T>` maps to `ProjectionEq(<A0 as Foo<A1..An>>::Item = T)`
30+
- `T: 'r` maps to `Outlives(T, 'r)`
31+
- `'a: 'b` maps to `Outlives('a, 'b)`
32+
33+
In the rules below, we will use `WC` to indicate where clauses that
34+
appear in Rust syntax; we will then use the same `WC` to indicate
35+
where those where clauses appear as goals in the program clauses that
36+
we are producing. In that case, the mapping above is used to convert
37+
from the Rust syntax into goals.
38+
39+
### Transforming the lowered where clauses
40+
41+
In addition, in the rules below, we sometimes do some transformations
42+
on the lowered where clauses, as defined here:
43+
44+
- `FromEnv(WC)` -- this indicates that:
45+
- `Implemented(TraitRef)` becomes `FromEnv(TraitRef)`
46+
- `ProjectionEq(Projection = Ty)` becomes `FromEnv(Projection = Ty)`
47+
- other where-clauses are left intact
48+
- `WellFormed(WC)` -- this indicates that:
49+
- `Implemented(TraitRef)` becomes `WellFormed(TraitRef)`
50+
- `ProjectionEq(Projection = Ty)` becomes `WellFormed(Projection = Ty)`
51+
52+
*TODO*: I suspect that we want to alter the outlives relations too,
53+
but Chalk isn't modeling those right now.
54+
55+
## Lowering traits
56+
57+
Given a trait definition
58+
59+
```rust
60+
trait Trait<P1..Pn> // P0 == Self
61+
where WC
62+
{
63+
// trait items
64+
}
65+
```
66+
67+
we will produce a number of declarations. This section is focused on
68+
the program clauses for the trait header (i.e., the stuff outside the
69+
`{}`); the [section on trait items](#trait-items) covers the stuff
70+
inside the `{}`.
71+
72+
### Trait header
73+
74+
From the trait itself we mostly make "meta" rules that setup the
75+
relationships between different kinds of domain goals. The first dush
76+
rule from the trait header creates the mapping between the `FromEnv`
77+
and `Implemented` predicates:
78+
79+
forall<Self, P1..Pn> {
80+
Implemented(Self: Trait<P1..Pn>) :- FromEnv(Self: Trait<P1..Pn)
81+
}
82+
83+
<a name="implied-bounds">
84+
85+
#### Implied bounds
86+
87+
The next few clauses have to do with implied bounds (see also
88+
[RFC 2089]). For each trait, we produce two clauses:
89+
90+
[RFC 2089]: https://rust-lang.github.io/rfcs/2089-implied-bounds.html
91+
92+
// For each where clause WC:
93+
forall<Self, P1..Pn> {
94+
FromEnv(WC) :- FromEnv(Self: Trait<P1..Pn)
95+
}
96+
97+
This clause says that if we are assuming that the trait holds, then we can also
98+
assume that it's where-clauses hold. It's perhaps useful to see an example:
99+
100+
```rust
101+
trait Eq: PartialEq { ... }
102+
```
103+
104+
In this case, the `PartialEq` supertrait is equivalent to a `where
105+
Self: PartialEq` where clause, in our simplified model. The program
106+
clause above therefore states that if we can prove `FromEnv(T: Eq)` --
107+
e.g., if we are in some function with `T: Eq` in its where clauses --
108+
then we also know that `FromEnv(T: PartialEq)`. Thus the set of things
109+
that follow from the environment are not only the **direct where
110+
clauses** but also things that follow from them.
111+
112+
The next rule is related; it defines what it means for a trait reference
113+
to be **well-formed**:
114+
115+
// For each where clause WC:
116+
forall<Self, P1..Pn> {
117+
WellFormed(Self: Trait<P1..Pn>) :- Implemented(Self: Trait<P1..Pn>), WellFormed(WC)
118+
}
119+
120+
This `WellFormed` rule states that `T: Trait` is well-formed if (a)
121+
`T: Trait` is implemented and (b) all the where-clauses declared on
122+
`Trait` are well-formed (and hence they are implemented). Remember
123+
that the `WellFormed` predicate is
124+
[coinductive](./traits-goals-and-clauses.html#coinductive); in this
125+
case, it is serving as a kind of "carrier" that allows us to enumerate
126+
all the where clauses that are transitively implied by `T: Trait`.
127+
128+
An example:
129+
130+
```rust
131+
trait Foo: A + Bar { }
132+
trait Bar: B + Foo { }
133+
trait A { }
134+
trait B { }
135+
```
136+
137+
Here, the transitive set of implications for `T: Foo` are `T: A`, `T: Bar`, and `T: B`.
138+
And indeed if we were to try to prove `WellFormed(T: Foo)`, we would have to prove each
139+
one of those:
140+
141+
- `WellFormed(T: Foo)`
142+
- `Implemented(T: Foo)`
143+
- `WellFormed(T: A)`
144+
- `Implemented(T: A)`
145+
- `WellFormed(T: Bar)`
146+
- `Implemented(T: Bar)`
147+
- `WellFormed(T: B)`
148+
- `Implemented(T: Bar)`
149+
- `WellFormed(T: Foo)` -- cycle, true coinductively
150+
151+
This `WellFormed` predicate is only used when proving that impls are
152+
well-formed -- basically, for each impl of some trait ref `TraitRef`,
153+
we must that `WellFormed(TraitRef)`. This in turn justifies the
154+
implied bounds rules that allow us to extend the set of `FromEnv`
155+
items.
156+
157+
<a name=trait-items>
158+
159+
## Lowering trait items
160+
161+
### Associated type declarations
162+
163+
Given a trait that declares a (possibly generic) associated type:
164+
165+
```rust
166+
trait Trait<P1..Pn> // P0 == Self
167+
where WC
168+
{
169+
type AssocType<Pn+1..Pm>: Bounds where WC1;
170+
}
171+
```
172+
173+
We will produce a number of program clases. The first two define
174+
the rules by which `ProjectionEq` can succeed; these two clauses are discussed
175+
in detail in the [section on associated types](./traits-associated-types.html).
176+
177+
// ProjectionEq can succeed by normalizing:
178+
forall<Self, P1..Pn, Pn+1..Pm, U> {
179+
ProjectionEq(<Self as Trait<P1..Pn>>::AssocType<Pn+1..Pm> = U) :-
180+
Normalize(<Self as Trait<P1..Pn>>::AssocType<Pn+1..Pm> -> U)
181+
}
182+
183+
// ProjectionEq can succeed by skolemizing:
184+
forall<Self, P1..Pn, Pn+1..Pm> {
185+
ProjectionEq(
186+
<Self as Trait<P1..Pn>>::AssocType<Pn+1..Pm> =
187+
(Trait::AssocType)<Self, P1..Pn, Pn+1..Pm>
188+
) :-
189+
// But only if the trait is implemented, and the conditions from
190+
// the associated type are met as well:
191+
Implemented(Self: Trait<P1..Pn>),
192+
WC1
193+
}
194+
195+
The next rule covers implied bounds for the projection. In particular,
196+
the `Bounds` declared on the associated type must be proven to hold to
197+
show that the impl is well-formed, and hence we can rely on them from
198+
elsewhere.
199+
200+
// XXX how exactly should we set this up? Have to be careful;
201+
// presumably this has to be a kind of `FromEnv` setup.
202+
203+
### Lowering function and constant declarations
204+
205+
Chalk didn't model functions and constants, but I would eventually
206+
like to treat them exactly like normalization. See [the section on function/constant
207+
values below](#constant-vals) for more details.
208+
209+
## Lowering impls
210+
211+
Given an impl of a trait:
212+
213+
```rust
214+
impl<P0..Pn> Trait<A1..An> for A0
215+
where WC
216+
{
217+
// zero or more impl items
218+
}
219+
```
220+
221+
Let `TraitRef` be the trait reference `A0: Trait<A1..An>`. Then we
222+
will create the following rules:
223+
224+
forall<P0..Pn> {
225+
Implemented(TraitRef) :- WC
226+
}
227+
228+
In addition, we will lower all of the *impl items*.
229+
230+
## Lowering impl items
231+
232+
### Associated type values
233+
234+
Given an impl that contains:
235+
236+
```rust
237+
impl<P0..Pn> Trait<A1..An> for A0
238+
where WC
239+
{
240+
type AssocType<Pn+1..Pm>: Bounds where WC1 = T;
241+
}
242+
```
243+
244+
We produce the following rule:
245+
246+
forall<P0..Pm> {
247+
forall<Pn+1..Pm> {
248+
Normalize(<A0 as Trait<A1..An>>::AssocType<Pn+1..Pm> -> T) :-
249+
WC, WC1
250+
}
251+
}
252+
253+
Note that `WC` and `WC1` both encode where-clauses that the impl can
254+
rely on, whereas the bounds `Bounds` on the associated type are things
255+
that the impl must prove (see the well-formedness checking).
256+
257+
<a name=constant-vals>
258+
259+
### Function and constant values
260+
261+
Chalk didn't model functions and constants, but I would eventually
262+
like to treat them exactly like normalization. This presumably
263+
involves adding a new kind of parameter (constant), and then having a
264+
`NormalizeValue` domain goal. This is *to be written* because the
265+
details are a bit up in the air.
266+
267+

‎src/traits-lowering-to-logic.md

+182
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
# Lowering to logic
2+
3+
The key observation here is that the Rust trait system is basically a
4+
kind of logic, and it can be mapped onto standard logical inference
5+
rules. We can then look for solutions to those inference rules in a
6+
very similar fashion to how e.g. a [Prolog] solver works. It turns out
7+
that we can't *quite* use Prolog rules (also called Horn clauses) but
8+
rather need a somewhat more expressive variant.
9+
10+
[Prolog]: https://en.wikipedia.org/wiki/Prolog
11+
12+
## Rust traits and logic
13+
14+
One of the first observations is that the Rust trait system is
15+
basically a kind of logic. As such, we can map our struct, trait, and
16+
impl declarations into logical inference rules. For the most part,
17+
these are basically Horn clauses, though we'll see that to capture the
18+
full richness of Rust -- and in particular to support generic
19+
programming -- we have to go a bit further than standard Horn clauses.
20+
21+
To see how this mapping works, let's start with an example. Imagine
22+
we declare a trait and a few impls, like so:
23+
24+
```rust
25+
trait Clone { }
26+
impl Clone for usize { }
27+
impl<T> Clone for Vec<T> where T: Clone { }
28+
```
29+
30+
We could map these declarations to some Horn clauses, written in a
31+
Prolog-like notation, as follows:
32+
33+
```
34+
Clone(usize).
35+
Clone(Vec<?T>) :- Clone(?T).
36+
```
37+
38+
In Prolog terms, we might say that `Clone(Foo)` -- where `Foo` is some
39+
Rust type -- is a *predicate* that represents the idea that the type
40+
`Foo` implements `Clone`. These rules are **program clauses**; they
41+
state the conditions under which that predicate can be proven (i.e.,
42+
considered true). So the first rule just says "Clone is implemented
43+
for `usize`". The next rule says "for any type `?T`, Clone is
44+
implemented for `Vec<?T>` if clone is implemented for `?T`". So
45+
e.g. if we wanted to prove that `Clone(Vec<Vec<usize>>)`, we would do
46+
so by applying the rules recursively:
47+
48+
- `Clone(Vec<Vec<usize>>)` is provable if:
49+
- `Clone(Vec<usize>)` is provable if:
50+
- `Clone(usize)` is provable. (Which is is, so we're all good.)
51+
52+
But now suppose we tried to prove that `Clone(Vec<Bar>)`. This would
53+
fail (after all, I didn't give an impl of `Clone` for `Bar`):
54+
55+
- `Clone(Vec<Bar>)` is provable if:
56+
- `Clone(Bar)` is provable. (But it is not, as there are no applicable rules.)
57+
58+
We can easily extend the example above to cover generic traits with
59+
more than one input type. So imagine the `Eq<T>` trait, which declares
60+
that `Self` is equatable with a value of type `T`:
61+
62+
```rust
63+
trait Eq<T> { ... }
64+
impl Eq<usize> for usize { }
65+
impl<T: Eq<U>> Eq<Vec<U>> for Vec<T> { }
66+
```
67+
68+
That could be mapped as follows:
69+
70+
```
71+
Eq(usize, usize).
72+
Eq(Vec<?T>, Vec<?U>) :- Eq(?T, ?U).
73+
```
74+
75+
So far so good.
76+
77+
## Type-checking normal functions
78+
79+
OK, now that we have defined some logical rules that are able to
80+
express when traits are implemented and to handle associated types,
81+
let's turn our focus a bit towards **type-checking**. Type-checking is
82+
interesting because it is what gives us the goals that we need to
83+
prove. That is, everything we've seen so far has been about how we
84+
derive the rules by which we can prove goals from the traits and impls
85+
in the program; but we are also interesting in how derive the goals
86+
that we need to prove, and those come from type-checking.
87+
88+
Consider type-checking the function `foo()` here:
89+
90+
```rust
91+
fn foo() { bar::<usize>() }
92+
fn bar<U: Eq>() { }
93+
```
94+
95+
This function is very simple, of course: all it does is to call
96+
`bar::<usize>()`. Now, looking at the definition of `bar()`, we can see
97+
that it has one where-clause `U: Eq`. So, that means that `foo()` will
98+
have to prove that `usize: Eq` in order to show that it can call `bar()`
99+
with `usize` as the type argument.
100+
101+
If we wanted, we could write a Prolog predicate that defines the
102+
conditions under which `bar()` can be called. We'll say that those
103+
conditions are called being "well-formed":
104+
105+
```
106+
barWellFormed(?U) :- Eq(?U).
107+
```
108+
109+
Then we can say that `foo()` type-checks if the reference to
110+
`bar::<usize>` (that is, `bar()` applied to the type `usize`) is
111+
well-formed:
112+
113+
```
114+
fooTypeChecks :- barWellFormed(usize).
115+
```
116+
117+
If we try to prove the goal `fooTypeChecks`, it will succeed:
118+
119+
- `fooTypeChecks` is provable if:
120+
- `barWellFormed(usize)`, which is provable if:
121+
- `Eq(usize)`, which is provable because of an impl.
122+
123+
Ok, so far so good. Let's move on to type-checking a more complex function.
124+
125+
## Type-checking generic functions: beyond Horn clauses
126+
127+
In the last section, we used standard Prolog horn-clauses (augmented with Rust's
128+
notion of type equality) to type-check some simple Rust functions. But that only
129+
works when we are type-checking non-generic functions. If we want to type-check
130+
a generic function, it turns out we need a stronger notion of goal than Prolog
131+
can be provide. To see what I'm talking about, let's revamp our previous
132+
example to make `foo` generic:
133+
134+
```rust
135+
fn foo<T: Eq>() { bar::<T>() }
136+
fn bar<U: Eq>() { }
137+
```
138+
139+
To type-check the body of `foo`, we need to be able to hold the type
140+
`T` "abstract". That is, we need to check that the body of `foo` is
141+
type-safe *for all types `T`*, not just for some specific type. We might express
142+
this like so:
143+
144+
```
145+
fooTypeChecks :-
146+
// for all types T...
147+
forall<T> {
148+
// ...if we assume that Eq(T) is provable...
149+
if (Eq(T)) {
150+
// ...then we can prove that `barWellFormed(T)` holds.
151+
barWellFormed(T)
152+
}
153+
}.
154+
```
155+
156+
This notation I'm using here is the notation I've been using in my
157+
prototype implementation; it's similar to standard mathematical
158+
notation but a bit Rustified. Anyway, the problem is that standard
159+
Horn clauses don't allow universal quantification (`forall`) or
160+
implication (`if`) in goals (though many Prolog engines do support
161+
them, as an extension). For this reason, we need to accept something
162+
called "first-order hereditary harrop" (FOHH) clauses -- this long
163+
name basically means "standard Horn clauses with `forall` and `if` in
164+
the body". But it's nice to know the proper name, because there is a
165+
lot of work describing how to efficiently handle FOHH clauses; see for
166+
example Gopalan Nadathur's excellent
167+
["A Proof Procedure for the Logic of Hereditary Harrop Formulas"][pphhf]
168+
in [the bibliography].
169+
170+
[the bibliography]: ./traits-bibliography.html
171+
[pphhf]: ./traits-bibliography.html#pphhf
172+
173+
It turns out that supporting FOHH is not really all that hard. And
174+
once we are able to do that, we can easily describe the type-checking
175+
rule for generic functions like `foo` in our logic.
176+
177+
## Source
178+
179+
This page is a lightly adapted version of a
180+
[blog post by Nicholas Matsakis][lrtl].
181+
182+
[lrtl]: http://smallcultfollowing.com/babysteps/blog/2017/01/26/lowering-rust-traits-to-logic/

‎src/traits-regions.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Region constraints
2+
3+
*to be written*

‎src/traits.md

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Trait solving (new-style)
2+
3+
🚧 This chapter describes "new-style" trait solving. This is still in
4+
the process of being implemented; this chapter serves as a kind of
5+
in-progress design document. If you would prefer to read about how the
6+
current trait solver works, check out
7+
[this chapter](./trait-resolution.html).🚧
8+
9+
Trait solving is based around a few key ideas:
10+
11+
- [Canonicalization](./traits-canonicalization.html), which allows us to
12+
extract types that contain inference variables in them from their
13+
inference context, work with them, and then bring the results back
14+
into the original context.
15+
- [Lowering to logic](./traits-lowering-to-logic.html), which expresses
16+
Rust traits in terms of standard logical terms.
17+
18+
*more to come*

‎src/type-inference.md

+2
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ The `tcx.infer_ctxt` method actually returns a build, which means
4343
there are some kinds of configuration you can do before the `infcx` is
4444
created. See `InferCtxtBuilder` for more information.
4545

46+
<a name=vars>
47+
4648
## Inference variables
4749

4850
The main purpose of the inference context is to house a bunch of

0 commit comments

Comments
 (0)
Please sign in to comment.