The “start” of the trait system is the canonical query (these are both queries in the more general sense of the word – something you would like to know the answer to – and in the rustc-specific sense). The idea is that the type checker or other parts of the system, may in the course of doing their thing want to know whether some trait is implemented for some type (e.g., is u32: Debug
true?). Or they may want to normalize some associated type.
This section covers queries at a fairly high level of abstraction. The subsections look a bit more closely at how these ideas are implemented in rustc.
In a traditional Prolog system, when you start a query, the solver will run off and start supplying you with every possible answer it can find. So given something like this:
?- Vec<i32>: AsRef<?U>
The solver might answer:
Vec<i32>: AsRef<[i32]> continue? (y/n)
This continue
bit is interesting. The idea in Prolog is that the solver is finding all possible instantiations of your query that are true. In this case, if we instantiate ?U = [i32]
, then the query is true (note that a traditional Prolog interface does not, directly, tell us a value for ?U
, but we can infer one by unifying the response with our original query – Rust's solver gives back a substitution instead). If we were to hit y
, the solver might then give us another possible answer:
Vec<i32>: AsRef<Vec<i32>> continue? (y/n)
This answer derives from the fact that there is a reflexive impl (impl<T> AsRef<T> for T
) for AsRef
. If were to hit y
again, then we might get back a negative response:
no
Naturally, in some cases, there may be no possible answers, and hence the solver will just give me back no
right away:
?- Box<i32>: Copy no
In some cases, there might be an infinite number of responses. So for example if I gave this query, and I kept hitting y
, then the solver would never stop giving me back answers:
?- Vec<?U>: Clone Vec<i32>: Clone continue? (y/n) Vec<Box<i32>>: Clone continue? (y/n) Vec<Box<Box<i32>>>: Clone continue? (y/n) Vec<Box<Box<Box<i32>>>>: Clone continue? (y/n)
As you can imagine, the solver will gleefully keep adding another layer of Box
until we ask it to stop, or it runs out of memory.
Another interesting thing is that queries might still have variables in them. For example:
?- Rc<?T>: Clone
might produce the answer:
Rc<?T>: Clone continue? (y/n)
After all, Rc<?T>
is true no matter what type ?T
is.
The trait queries in rustc work somewhat differently. Instead of trying to enumerate all possible answers for you, they are looking for an unambiguous answer. In particular, when they tell you the value for a type variable, that means that this is the only possible instantiation that you could use, given the current set of impls and where-clauses, that would be provable.
The response to a trait query in rustc is typically a Result<QueryResult<T>, NoSolution>
(where the T
will vary a bit depending on the query itself). The Err(NoSolution)
case indicates that the query was false and had no answers (e.g., Box<i32>: Copy
). Otherwise, the QueryResult
gives back information about the possible answer(s) we did find. It consists of four parts:
Proven
means that the result is known to be true.Vec<i32>: Clone
, say, or Rc<?T>: Clone
.Ambiguous
means that there were things we could not yet prove to be either true or false, typically because more type information was needed. (We'll see an example shortly.)Vec<?T>: Clone
.?T
) that appeared in your original query. (Remember that in Prolog, we had to infer these.)Ambiguous
cases.T
. For some specialized queries – like normalizing associated types – this is used to carry back an extra result, but it's often just ()
.Let‘s work through an example query to see what all the parts mean. Consider the Borrow
trait. This trait has a number of impls; among them, there are these two (for clarity, I’ve written the Sized
bounds explicitly):
impl<T> Borrow<T> for T where T: ?Sized impl<T> Borrow<[T]> for Vec<T> where T: Sized
Example 1. Imagine we are type-checking this (rather artificial) bit of code:
fn foo<A, B>(a: A, vec_b: Option<B>) where A: Borrow<B> { } fn main() { let mut t: Vec<_> = vec![]; // Type: Vec<?T> let mut u: Option<_> = None; // Type: Option<?U> foo(t, u); // Example 1: requires `Vec<?T>: Borrow<?U>` ... }
As the comments indicate, we first create two variables t
and u
; t
is an empty vector and u
is a None
option. Both of these variables have unbound inference variables in their type: ?T
represents the elements in the vector t
and ?U
represents the value stored in the option u
. Next, we invoke foo
; comparing the signature of foo
to its arguments, we wind up with A = Vec<?T>
and B = ?U
. Therefore, the where clause on foo
requires that Vec<?T>: Borrow<?U>
. This is thus our first example trait query.
There are many possible solutions to the query Vec<?T>: Borrow<?U>
; for example:
?U = Vec<?T>
,?U = [?T]
,?T = u32, ?U = [u32]
Therefore, the result we get back would be as follows (I'm going to ignore region constraints and the “value”):
Ambiguous
– we're not sure yet if this holds[?T = ?T, ?U = ?U]
– we learned nothing about the values of the variablesIn short, the query result says that it is too soon to say much about whether this trait is proven. During type-checking, this is not an immediate error: instead, the type checker would hold on to this requirement (Vec<?T>: Borrow<?U>
) and wait. As we'll see in the next example, it may happen that ?T
and ?U
wind up constrained from other sources, in which case we can try the trait query again.
Example 2. We can now extend our previous example a bit, and assign a value to u
:
fn foo<A, B>(a: A, vec_b: Option<B>) where A: Borrow<B> { } fn main() { // What we saw before: let mut t: Vec<_> = vec![]; // Type: Vec<?T> let mut u: Option<_> = None; // Type: Option<?U> foo(t, u); // `Vec<?T>: Borrow<?U>` => ambiguous // New stuff: u = Some(vec![]); // ?U = Vec<?V> }
As a result of this assignment, the type of u
is forced to be Option<Vec<?V>>
, where ?V
represents the element type of the vector. This in turn implies that ?U
is unified to Vec<?V>
.
Let's suppose that the type checker decides to revisit the “as-yet-unproven” trait obligation we saw before, Vec<?T>: Borrow<?U>
. ?U
is no longer an unbound inference variable; it now has a value, Vec<?V>
. So, if we “refresh” the query with that value, we get:
Vec<?T>: Borrow<Vec<?V>>
This time, there is only one impl that applies, the reflexive impl:
impl<T> Borrow<T> for T where T: ?Sized
Therefore, the trait checker will answer:
Proven
[?T = ?T, ?V = ?T]
Here, it is saying that we have indeed proven that the obligation holds, and we also know that ?T
and ?V
are the same type (but we don't know what that type is yet!).
(In fact, as the function ends here, the type checker would give an error at this point, since the element types of t
and u
are still not yet known, even though they are known to be the same.)