This document describes the general process and points out some non-obvious
This chapter describes the general process of _trait resolution_ and points out
things.
some non-obvious things.
**WARNING:** This material was moved verbatim from a rustc README, so
it may not "fit" the style of the guide until it is adapted.
## Major concepts
## Major concepts
@ -21,12 +18,12 @@ and then a call to that function:
let v: Vec<isize> = clone_slice(&[1, 2, 3])
let v: Vec<isize> = clone_slice(&[1, 2, 3])
```
```
it is the job of trait resolution to figure out (in which case)
it is the job of trait resolution to figure out whether there exists an impl of
whether there exists an impl of `isize : Clone`
(in this case) `isize : Clone`.
Note that in some cases, like generic functions, we may not be able to
Note that in some cases, like generic functions, we may not be able to
find a specific impl, but we can figure out that the caller must
find a specific impl, but we can figure out that the caller must
provide an impl. To see what I mean, consider the body of `clone_slice`:
provide an impl. For example, consider the body of `clone_slice`:
```rust
```rust
fn clone_slice<T:Clone>(x: &[T]) -> Vec<T> {
fn clone_slice<T:Clone>(x: &[T]) -> Vec<T> {
@ -43,26 +40,33 @@ is, we can't find the specific impl; but based on the bound `T:Clone`,
we can say that there exists an impl which the caller must provide.
we can say that there exists an impl which the caller must provide.
We use the term *obligation* to refer to a trait reference in need of
We use the term *obligation* to refer to a trait reference in need of
an impl.
an impl. Basically, the trait resolution system resolves an obligation
by proving that an appropriate impl does exist.
During type checking, we do not store the results of trait selection.
We simply wish to verify that trait selection will succeed. Then
later, at trans time, when we have all concrete types available, we
can repeat the trait selection to choose an actual implementation, which
will then be generated in the output binary.
## Overview
## Overview
Trait resolution consists of three major parts:
Trait resolution consists of three major parts:
- SELECTION: Deciding how to resolve a specific obligation. For
- **Selection** is deciding how to resolve a specific obligation. For
example, selection might decide that a specific obligation can be
example, selection might decide that a specific obligation can be
resolved by employing an impl which matches the self type, or by
resolved by employing an impl which matches the `Self` type, or by
using a parameter bound. In the case of an impl, Selecting one
using a parameter bound (e.g. `T: Trait`). In the case of an impl, selecting one
obligation can create *nested obligations* because of where clauses
obligation can create *nested obligations* because of where clauses
on the impl itself. It may also require evaluating those nested
on the impl itself. It may also require evaluating those nested
obligations to resolve ambiguities.
obligations to resolve ambiguities.
- FULFILLMENT: The fulfillment code is what tracks that obligations
- **Fulfillment** is keeping track of which obligations
are completely fulfilled. Basically it is a worklist of obligations
are completely fulfilled. Basically, it is a worklist of obligations
to be selected: once selection is successful, the obligation is
to be selected: once selection is successful, the obligation is
removed from the worklist and any nested obligations are enqueued.
removed from the worklist and any nested obligations are enqueued.
- COHERENCE: The coherence checks are intended to ensure that there
- **Coherence** checks are intended to ensure that there
are never overlapping impls, where two impls could be used with
are never overlapping impls, where two impls could be used with
equal precedence.
equal precedence.
@ -83,12 +87,21 @@ and returns a `SelectionResult`. There are three possible outcomes:
contains unbound type variables.
contains unbound type variables.
- `Err(err)`– the obligation definitely cannot be resolved due to a
- `Err(err)`– the obligation definitely cannot be resolved due to a
type error, or because there are no impls that could possibly apply,
type error or because there are no impls that could possibly apply.
etc.
The basic algorithm for selection is broken into two big phases:
The basic algorithm for selection is broken into two big phases:
candidate assembly and confirmation.
candidate assembly and confirmation.
Note that because of how lifetime inference works, it is not possible to
give back immediate feedback as to whether a unification or subtype
relationship between lifetimes holds or not. Therefore, lifetime
matching is *not* considered during selection. This is reflected in
the fact that subregion assignment is infallible. This may yield
lifetime constraints that will later be found to be in error (in
contrast, the non-lifetime-constraints have already been checked
during selection and can never cause an error, though naturally they
may lead to other errors downstream).
### Candidate assembly
### Candidate assembly
Searches for impls/where-clauses/etc that might
Searches for impls/where-clauses/etc that might
@ -98,6 +111,15 @@ candidate that is definitively applicable. In some cases, we may not
know whether an impl/where-clause applies or not – this occurs when
know whether an impl/where-clause applies or not – this occurs when
the obligation contains unbound inference variables.
the obligation contains unbound inference variables.
The subroutines that decide whether a particular impl/where-clause/etc
applies to a particular obligation are collectively refered to as the
process of _matching_. At the moment, this amounts to
unifying the `Self` types, but in the future we may also recursively
consider some of the nested obligations, in the case of an impl.
**TODO**: what does "unifying the `Self` types" mean? The `Self` of the
obligation with that of an impl?
The basic idea for candidate assembly is to do a first pass in which
The basic idea for candidate assembly is to do a first pass in which
we identify all possible candidates. During this pass, all that we do
we identify all possible candidates. During this pass, all that we do
is try and unify the type parameters. (In particular, we ignore any
is try and unify the type parameters. (In particular, we ignore any
@ -141,16 +163,14 @@ let y = x.convert();
The call to convert will generate a trait reference `Convert<$Y> for
The call to convert will generate a trait reference `Convert<$Y> for
isize`, where `$Y` is the type variable representing the type of
isize`, where `$Y` is the type variable representing the type of
`y`. When we match this against the two impls we can see, we will find
`y`. Of the two impls we can see, the only one that matches is
that only one remains: `Convert<usize> for isize`. Therefore, we can
`Convert<usize> for isize`. Therefore, we can
select this impl, which will cause the type of `$Y` to be unified to
select this impl, which will cause the type of `$Y` to be unified to
`usize`. (Note that while assembling candidates, we do the initial
`usize`. (Note that while assembling candidates, we do the initial
unifications in a transaction, so that they don't affect one another.)
unifications in a transaction, so that they don't affect one another.)
There are tests to this effect in src/test/run-pass:
**TODO**: The example says we can "select" the impl, but this section is talking specifically about candidate assembly. Does this mean we can sometimes skip confirmation? Or is this poor wording?
**TODO**: Is the unification of `$Y` part of trait resolution or type inference? Or is this not the same type of "inference variable" as in type inference?