Skip to content

Commit c3a3502

Browse files
committed
add background material on trait queries
1 parent 3c8a827 commit c3a3502

File tree

5 files changed

+241
-5
lines changed

5 files changed

+241
-5
lines changed

src/SUMMARY.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,14 @@
2323
- [Specialization](./trait-specialization.md)
2424
- [Trait solving (new-style)](./traits.md)
2525
- [Lowering to logic](./traits-lowering-to-logic.md)
26-
- [Canonical queries](./traits-canonicalization.md)
27-
- [Goals and clauses](./traits-goals-and-clauses.md)
28-
- [Lowering rules](./traits-lowering-rules.md)
26+
- [Goals and clauses](./traits-goals-and-clauses.md)
27+
- [Lowering rules](./traits-lowering-rules.md)
28+
- [Canonical queries](./traits-canonical-queries.md)
29+
- [Canonicalization](./traits-canonicalization.md)
2930
- [Equality and associated types](./traits-associated-types.md)
3031
- [Region constraints](./traits-regions.md)
3132
- [Well-formedness checking](./traits-wf.md)
33+
- [The SLG solver](./traits-slg.md)
3234
- [Bibliography](./traits-bibliography.md)
3335
- [Type checking](./type-checking.md)
3436
- [The MIR (Mid-level IR)](./mir.md)

src/traits-canonical-queries.md

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
# Canonical queries
2+
3+
The "start" of the trait system is the **canonical query** (these are
4+
both queries in the more general sense of the word -- something you
5+
would like to know the answer to -- and in the
6+
[rustc-specific sense](./query.html)). The idea is that the type
7+
checker or other parts of the system, may in the course of doing their
8+
thing want to know whether some trait is implemented for some type
9+
(e.g., is `u32: Debug` true?). Or they may want to
10+
[normalize some associated type](./traits-associated-types.html).
11+
12+
This section covers queries at a fairly high level of abstraction. The
13+
subsections look a bit more closely at how these ideas are implemented
14+
in rustc.
15+
16+
## The traditional, interactive Prolog query
17+
18+
In a traditional Prolog system, when you start a query, the solver
19+
will run off and start supplying you with every possible answer it can
20+
find. So given something like this:
21+
22+
?- Vec<i32>: AsRef<?U>
23+
24+
The solver might answer:
25+
26+
Vec<i32>: AsRef<[i32]>
27+
continue? (y/n)
28+
29+
This `continue` bit is interesting. The idea in Prolog is that the
30+
solver is finding **all possible** instantiations of your query that
31+
are true. In this case, if we instantiate `?U = [i32]`, then the query
32+
is true (note that a traditional Prolog interface does not, directly,
33+
tell us a value for `?U`, but we can infer one by unifying the
34+
response with our original query -- Rust's solver gives back a
35+
substitution instead). If we were to hit `y`, the solver might then
36+
give us another possible answer:
37+
38+
Vec<i32>: AsRef<Vec<i32>>
39+
continue? (y/n)
40+
41+
This answer derives from the fact that there is a reflexive impl
42+
(`impl<T> AsRef<T> for T`) for `AsRef`. If were to hit `y` again,
43+
then we might get back a negative response:
44+
45+
no
46+
47+
Naturally, in some cases, there may be no possible answers, and hence
48+
the solver will just give me back `no` right away:
49+
50+
?- Box<i32>: Copy
51+
no
52+
53+
In some cases, there might be an infinite number of responses. So for
54+
example if I gave this query, and I kept hitting `y`, then the solver
55+
would never stop giving me back answers:
56+
57+
?- Vec<?U>: Clone
58+
Vec<i32>: Clone
59+
continue? (y/n)
60+
Vec<Box<i32>>: Clone
61+
continue? (y/n)
62+
Vec<Box<Box<i32>>>: Clone
63+
continue? (y/n)
64+
Vec<Box<Box<Box<i32>>>>: Clone
65+
continue? (y/n)
66+
67+
As you can imagine, the solver will gleefully keep adding another
68+
layer of `Box` until we ask it to stop, or it runs out of memory.
69+
70+
Another interesting thing is that queries might still have variables
71+
in them. For example:
72+
73+
?- Rc<?T>: Clone
74+
75+
might produce the answer:
76+
77+
Rc<?T>: Clone
78+
continue? (y/n)
79+
80+
After all, `Rc<?T>` is true **no matter what type `?T` is**.
81+
82+
## A trait query in rustc
83+
84+
The trait queries in rustc work somewhat differently. Instead of
85+
trying to enumerate **all possible** answers for you, they are looking
86+
for an **unambiguous** answer. In particular, when they tells you the
87+
value for a type variable, that means that this is the **only possible
88+
instantiation** that you could use, given the current set of impls and
89+
where-clauses, that would be provable. (Internally within the solver,
90+
though, they can potentially enumerate all possible answers. See
91+
[the description of the SLG solver](./traits-slg.html) for details.)
92+
93+
The response to a trait query in rustc is typically a
94+
`Result<QueryResult<T>, NoSolution>` (where the `T` will vary a bit
95+
depending on the query itself). The `Err(NoSolution)` case indicates
96+
that the query was false and had no answers (e.g., `Box<i32>: Copy`).
97+
Otherwise, the `QueryResult` gives back information about the possible answer(s)
98+
we did find. It consists of four parts:
99+
100+
- **Certainty:** tells you how sure we are of this answer. It can have two values:
101+
- `Proven` means that the result is known to be true.
102+
- This might be the result for trying to prove `Vec<i32>: Clone`,
103+
say, or `Rc<?T>: Clone`.
104+
- `Ambiguous` means that there were things we could not yet prove to
105+
be either true *or* false, typically because more type information
106+
was needed. (We'll see an example shortly.)
107+
- This might be the result for trying to prove `Vec<?T>: Clone`.
108+
- **Var values:** Values for each of the unbound inference variables
109+
(like `?T`) that appeared in your original query. (Remember that in Prolog,
110+
we had to infer these.)
111+
- As we'll see in the example below, we can get back var values even
112+
for `Ambiguous` cases.
113+
- **Region constraints:** these are relations that must hold between
114+
the lifetimes that you supplied as inputs. We'll ignore these here,
115+
but see the
116+
[section on handling regions in traits](./traits-regions.html) for
117+
more details.
118+
- **Value:** The query result also comes with a value of type `T`. For
119+
some specialized queries -- like normalizing associated types --
120+
this is used to carry back an extra result, but it's often just
121+
`()`.
122+
123+
### Examples
124+
125+
Let's work through an example query to see what all the parts mean.
126+
Consider [the `Borrow` trait][borrow]. This trait has a number of
127+
impls; among them, there are these two (for clarify, I've written the
128+
`Sized` bounds explicitly):
129+
130+
[borrow]: https://doc.rust-lang.org/std/borrow/trait.Borrow.html
131+
132+
```rust
133+
impl<T> Borrow<T> for T where T: ?Sized
134+
impl<T> Borrow<[T]> for Vec<T> where T: Sized
135+
```
136+
137+
**Example 1.** Imagine we are type-checking this (rather artificial)
138+
bit of code:
139+
140+
```rust
141+
fn foo<A, B>(a: A, vec_b: Option<B>) where A: Borrow<B> { }
142+
143+
fn main() {
144+
let mut t: Vec<_> = vec![]; // Type: Vec<?T>
145+
let mut u: Option<_> = None; // Type: Option<?U>
146+
foo(t, u); // Example 1: requires `Vec<?T>: Borrow<?U>`
147+
...
148+
}
149+
```
150+
151+
As the comments indicate, we first create two variables `t` and `u`;
152+
`t` is an empty vector and `u` is a `None` option. Both of these
153+
variables have unbound inference variables in their type: `?T`
154+
represents the elements in the vector `t` and `?U` represents the
155+
value stored in the option `u`. Next, we invoke `foo`; comparing the
156+
signature of `foo` to its arguments, we wind up with `A = Vec<?T>` and
157+
`B = ?U`.Therefore, the where clause on `foo` requires that `Vec<?T>:
158+
Borrow<?U>`. This is thus our first example trait query.
159+
160+
There are many possible solutions to the query `Vec<?T>: Borrow<?U>`;
161+
for example:
162+
163+
- `?U = Vec<?T>`,
164+
- `?U = [?T]`,
165+
- `?T = u32, ?U = [u32]`
166+
- and so forth.
167+
168+
Therefore, the result we get back would be as follows (I'm going to
169+
ignore region constraints and the "value"):
170+
171+
- Certainty: `Ambiguous` -- we're not sure yet if this holds
172+
- Var values: `[?T = ?T, ?U = ?U]` -- we learned nothing about the values of the variables
173+
174+
In short, the query result says that it is too soon to say much about
175+
whether this trait is proven. During type-checking, this is not an
176+
immediate error: instead, the type checker would hold on to this
177+
requirement (`Vec<?T>: Borrow<?U>`) and wait. As we'll see in the next
178+
example, it may happen that `?T` and `?U` wind up constrained from
179+
other sources, in which case we can try the trait query again.
180+
181+
**Example 2.** We can now extend our previous example a bit,
182+
and assign a value to `u`:
183+
184+
```rust
185+
fn foo<A, B>(a: A, vec_b: Option<B>) where A: Borrow<B> { }
186+
187+
fn main() {
188+
// What we saw before:
189+
let mut t: Vec<_> = vec![]; // Type: Vec<?T>
190+
let mut u: Option<_> = None; // Type: Option<?U>
191+
foo(t, u); // `Vec<?T>: Borrow<?U>` => ambiguous
192+
193+
// New stuff:
194+
u = Some(vec![]); // ?U = Vec<?V>
195+
}
196+
```
197+
198+
As a result of this assignment, the type of `u` is forced to be
199+
`Option<Vec<?V>>`, where `?V` represents the element type of the
200+
vector. This in turn implies that `?U` is [unified] to `Vec<?V>`.
201+
202+
[unified]: ./type-checking.html
203+
204+
Let's suppose that the type checker decides to revisit the
205+
"as-yet-unproven" trait obligation we saw before, `Vec<?T>:
206+
Borrow<?U>`. `?U` is no longer an unbound inference variable; it now
207+
has a value, &. So, if we "refresh" the query with that value, we get:
208+
209+
Vec<?T>: Borrow<Vec<?V>>
210+
211+
This time, there is only one impl that applies, the reflexive impl:
212+
213+
impl<T> Borrow<T> for T where T: ?Sized
214+
215+
Therefore, the trait checker will answer:
216+
217+
- Certainty: `Proven`
218+
- Var values: `[?T = ?T, ?V = ?T]`
219+
220+
Here, it is saying that we have indeed proven that the obligation
221+
holds, and we also know that `?T` and `?V` are the same type (but we
222+
don't know what that type is yet!).
223+
224+
(In fact, as the function ends here, the type checker would give an
225+
error at this point, since the element types of `t` and `u` are still
226+
not yet known, even though they are known to be the same.)
227+
228+

src/traits-canonicalization.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
# Canonicalization
22

33
Canonicalization is the process of **isolating** an inference value
4-
from its context. It is really based on a very simple concept: every
4+
from its context. It is a key part of implementing
5+
[canonical queries][cq].
6+
7+
Canonicalization is really based on a very simple concept: every
58
[inference variable](./type-inference.html#vars) is always in one of
69
two states: either it is **unbound**, in which case we don't know yet
710
what type it is, or it is **bound**, in which case we do. So to
@@ -12,6 +15,8 @@ starting from zero and numbered in a fixed order (left to right, for
1215
the most part, but really it doesn't matter as long as it is
1316
consistent).
1417

18+
[cq]: ./traits-canonical-queries.html
19+
1520
So, for example, if we have the type `X = (?T, ?U)`, where `?T` and
1621
`?U` are distinct, unbound inference variables, then the canonical
1722
form of `X` would be `(?0, ?1)`, where `?0` and `?1` represent these

src/traits-slg.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# The SLG solver

src/traits.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ Trait solving is based around a few key ideas:
2424
- [Lazy normalization](./traits-associated-types.html), which is the
2525
technique we use to accommodate associated types when figuring out
2626
whether types are equal.
27-
- [Region constraints](./traits-regions.md), which are accumulated
27+
- [Region constraints](./traits-regions.html), which are accumulated
2828
during trait solving but mostly ignored. This means that trait
2929
solving effectively ignores the precise regions involved, always --
3030
but we still remember the constraints on them so that those

0 commit comments

Comments
 (0)