Convert gigantic comment away from //! form. It is annoying to

read (`//!` is intrusive) and annoying to edit (must maintain a prefix
on every line). Since the only purpose of a `doc.rs` file is to have a
bunch of text, using `/*!` and `*/` without indentations seems
appropriate.
This commit is contained in:
Niko Matsakis 2014-12-15 05:44:59 -05:00
parent f45c0ef51e
commit dab6e70e03

View File

@ -8,399 +8,405 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! # TRAIT RESOLUTION
//!
//! This document describes the general process and points out some non-obvious
//! things.
//!
//! ## Major concepts
//!
//! Trait resolution is the process of pairing up an impl with each
//! reference to a trait. So, for example, if there is a generic function like:
//!
//! fn clone_slice<T:Clone>(x: &[T]) -> Vec<T> { ... }
//!
//! and then a call to that function:
//!
//! let v: Vec<int> = clone_slice([1, 2, 3].as_slice())
//!
//! it is the job of trait resolution to figure out (in which case)
//! whether there exists an impl of `int : Clone`
//!
//! 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
//! provide an impl. To see what I mean, consider the body of `clone_slice`:
//!
//! fn clone_slice<T:Clone>(x: &[T]) -> Vec<T> {
//! let mut v = Vec::new();
//! for e in x.iter() {
//! v.push((*e).clone()); // (*)
//! }
//! }
//!
//! The line marked `(*)` is only legal if `T` (the type of `*e`)
//! implements the `Clone` trait. Naturally, since we don't know what `T`
//! 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 use the term *obligation* to refer to a trait reference in need of
//! an impl.
//!
//! ## Overview
//!
//! Trait resolution consists of three major parts:
//!
//! - SELECTION: Deciding how to resolve a specific obligation. For
//! example, selection might decide that a specific obligation can be
//! resolved by employing an impl which matches the self type, or by
//! using a parameter bound. In the case of an impl, Selecting one
//! obligation can create *nested obligations* because of where clauses
//! on the impl itself. It may also require evaluating those nested
//! obligations to resolve ambiguities.
//!
//! - FULFILLMENT: The fulfillment code is what tracks that obligations
//! are completely fulfilled. Basically it is a worklist of obligations
//! to be selected: once selection is successful, the obligation is
//! removed from the worklist and any nested obligations are enqueued.
//!
//! - COHERENCE: The coherence checks are intended to ensure that there
//! are never overlapping impls, where two impls could be used with
//! equal precedence.
//!
//! ## Selection
//!
//! Selection is the process of deciding whether an obligation can be
//! resolved and, if so, how it is to be resolved (via impl, where clause, etc).
//! The main interface is the `select()` function, which takes an obligation
//! and returns a `SelectionResult`. There are three possible outcomes:
//!
//! - `Ok(Some(selection))` -- yes, the obligation can be resolved, and
//! `selection` indicates how. If the impl was resolved via an impl,
//! then `selection` may also indicate nested obligations that are required
//! by the impl.
//!
//! - `Ok(None)` -- we are not yet sure whether the obligation can be
//! resolved or not. This happens most commonly when the obligation
//! contains unbound type variables.
//!
//! - `Err(err)` -- the obligation definitely cannot be resolved due to a
//! type error, or because there are no impls that could possibly apply,
//! etc.
//!
//! The basic algorithm for selection is broken into two big phases:
//! candidate assembly and confirmation.
//!
//! ### Candidate assembly
//!
//! Searches for impls/where-clauses/etc that might
//! possibly be used to satisfy the obligation. Each of those is called
//! a candidate. To avoid ambiguity, we want to find exactly one
//! candidate that is definitively applicable. In some cases, we may not
//! know whether an impl/where-clause applies or not -- this occurs when
//! the obligation contains unbound inference variables.
//!
//! 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
//! is try and unify the type parameters. (In particular, we ignore any
//! nested where clauses.) Presuming that this unification succeeds, the
//! impl is added as a candidate.
//!
//! Once this first pass is done, we can examine the set of candidates. If
//! it is a singleton set, then we are done: this is the only impl in
//! scope that could possibly apply. Otherwise, we can winnow down the set
//! of candidates by using where clauses and other conditions. If this
//! reduced set yields a single, unambiguous entry, we're good to go,
//! otherwise the result is considered ambiguous.
//!
//! #### The basic process: Inferring based on the impls we see
//!
//! This process is easier if we work through some examples. Consider
//! the following trait:
//!
//! ```
//! trait Convert<Target> {
//! fn convert(&self) -> Target;
//! }
//! ```
//!
//! This trait just has one method. It's about as simple as it gets. It
//! converts from the (implicit) `Self` type to the `Target` type. If we
//! wanted to permit conversion between `int` and `uint`, we might
//! implement `Convert` like so:
//!
//! ```rust
//! impl Convert<uint> for int { ... } // int -> uint
//! impl Convert<int> for uint { ... } // uint -> uint
//! ```
//!
//! Now imagine there is some code like the following:
//!
//! ```rust
//! let x: int = ...;
//! let y = x.convert();
//! ```
//!
//! The call to convert will generate a trait reference `Convert<$Y> for
//! int`, 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
//! that only one remains: `Convert<uint> for int`. Therefore, we can
//! select this impl, which will cause the type of `$Y` to be unified to
//! `uint`. (Note that while assembling candidates, we do the initial
//! unifications in a transaction, so that they don't affect one another.)
//!
//! There are tests to this effect in src/test/run-pass:
//!
//! traits-multidispatch-infer-convert-source-and-target.rs
//! traits-multidispatch-infer-convert-target.rs
//!
//! #### Winnowing: Resolving ambiguities
//!
//! But what happens if there are multiple impls where all the types
//! unify? Consider this example:
//!
//! ```rust
//! trait Get {
//! fn get(&self) -> Self;
//! }
//!
//! impl<T:Copy> Get for T {
//! fn get(&self) -> T { *self }
//! }
//!
//! impl<T:Get> Get for Box<T> {
//! fn get(&self) -> Box<T> { box get_it(&**self) }
//! }
//! ```
//!
//! What happens when we invoke `get_it(&box 1_u16)`, for example? In this
//! case, the `Self` type is `Box<u16>` -- that unifies with both impls,
//! because the first applies to all types, and the second to all
//! boxes. In the olden days we'd have called this ambiguous. But what we
//! do now is do a second *winnowing* pass that considers where clauses
//! and attempts to remove candidates -- in this case, the first impl only
//! applies if `Box<u16> : Copy`, which doesn't hold. After winnowing,
//! then, we are left with just one candidate, so we can proceed. There is
//! a test of this in `src/test/run-pass/traits-conditional-dispatch.rs`.
//!
//! #### Matching
//!
//! The subroutines that decide whether a particular impl/where-clause/etc
//! applies to a particular obligation. 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.
//!
//! #### Lifetimes and selection
//!
//! Because of how that 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).
//!
//! #### Where clauses
//!
//! Besides an impl, the other major way to resolve an obligation is via a
//! where clause. The selection process is always given a *parameter
//! environment* which contains a list of where clauses, which are
//! basically obligations that can assume are satisfiable. We will iterate
//! over that list and check whether our current obligation can be found
//! in that list, and if so it is considered satisfied. More precisely, we
//! want to check whether there is a where-clause obligation that is for
//! the same trait (or some subtrait) and for which the self types match,
//! using the definition of *matching* given above.
//!
//! Consider this simple example:
//!
//! trait A1 { ... }
//! trait A2 : A1 { ... }
//!
//! trait B { ... }
//!
//! fn foo<X:A2+B> { ... }
//!
//! Clearly we can use methods offered by `A1`, `A2`, or `B` within the
//! body of `foo`. In each case, that will incur an obligation like `X :
//! A1` or `X : A2`. The parameter environment will contain two
//! where-clauses, `X : A2` and `X : B`. For each obligation, then, we
//! search this list of where-clauses. To resolve an obligation `X:A1`,
//! we would note that `X:A2` implies that `X:A1`.
//!
//! ### Confirmation
//!
//! Confirmation unifies the output type parameters of the trait with the
//! values found in the obligation, possibly yielding a type error. If we
//! return to our example of the `Convert` trait from the previous
//! section, confirmation is where an error would be reported, because the
//! impl specified that `T` would be `uint`, but the obligation reported
//! `char`. Hence the result of selection would be an error.
//!
//! ### Selection during translation
//!
//! 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. In this case, we do not consider any
//! where-clauses to be in scope. We know that therefore each resolution
//! will resolve to a particular impl.
//!
//! One interesting twist has to do with nested obligations. In general, in trans,
//! we only need to do a "shallow" selection for an obligation. That is, we wish to
//! identify which impl applies, but we do not (yet) need to decide how to select
//! any nested obligations. Nonetheless, we *do* currently do a complete resolution,
//! and that is because it can sometimes inform the results of type inference. That is,
//! we do not have the full substitutions in terms of the type varibales of the impl available
//! to us, so we must run trait selection to figure everything out.
//!
//! Here is an example:
//!
//! trait Foo { ... }
//! impl<U,T:Bar<U>> Foo for Vec<T> { ... }
//!
//! impl Bar<uint> for int { ... }
//!
//! After one shallow round of selection for an obligation like `Vec<int>
//! : Foo`, we would know which impl we want, and we would know that
//! `T=int`, but we do not know the type of `U`. We must select the
//! nested obligation `int : Bar<U>` to find out that `U=uint`.
//!
//! It would be good to only do *just as much* nested resolution as
//! necessary. Currently, though, we just do a full resolution.
//!
//! ## Method matching
//!
//! Method dispach follows a slightly different path than normal trait
//! selection. This is because it must account for the transformed self
//! type of the receiver and various other complications. The procedure is
//! described in `select.rs` in the "METHOD MATCHING" section.
//!
//! # Caching and subtle considerations therewith
//!
//! In general we attempt to cache the results of trait selection. This
//! is a somewhat complex process. Part of the reason for this is that we
//! want to be able to cache results even when all the types in the trait
//! reference are not fully known. In that case, it may happen that the
//! trait selection process is also influencing type variables, so we have
//! to be able to not only cache the *result* of the selection process,
//! but *replay* its effects on the type variables.
//!
//! ## An example
//!
//! The high-level idea of how the cache works is that we first replace
//! all unbound inference variables with skolemized versions. Therefore,
//! if we had a trait reference `uint : Foo<$1>`, where `$n` is an unbound
//! inference variable, we might replace it with `uint : Foo<%0>`, where
//! `%n` is a skolemized type. We would then look this up in the cache.
//! If we found a hit, the hit would tell us the immediate next step to
//! take in the selection process: i.e., apply impl #22, or apply where
//! clause `X : Foo<Y>`. Let's say in this case there is no hit.
//! Therefore, we search through impls and where clauses and so forth, and
//! we come to the conclusion that the only possible impl is this one,
//! with def-id 22:
//!
//! impl Foo<int> for uint { ... } // Impl #22
//!
//! We would then record in the cache `uint : Foo<%0> ==>
//! ImplCandidate(22)`. Next we would confirm `ImplCandidate(22)`, which
//! would (as a side-effect) unify `$1` with `int`.
//!
//! Now, at some later time, we might come along and see a `uint :
//! Foo<$3>`. When skolemized, this would yield `uint : Foo<%0>`, just as
//! before, and hence the cache lookup would succeed, yielding
//! `ImplCandidate(22)`. We would confirm `ImplCandidate(22)` which would
//! (as a side-effect) unify `$3` with `int`.
//!
//! ## Where clauses and the local vs global cache
//!
//! One subtle interaction is that the results of trait lookup will vary
//! depending on what where clauses are in scope. Therefore, we actually
//! have *two* caches, a local and a global cache. The local cache is
//! attached to the `ParameterEnvironment` and the global cache attached
//! to the `tcx`. We use the local cache whenever the result might depend
//! on the where clauses that are in scope. The determination of which
//! cache to use is done by the method `pick_candidate_cache` in
//! `select.rs`.
//!
//! There are two cases where we currently use the local cache. The
//! current rules are probably more conservative than necessary.
//!
//! ### Trait references that involve parameter types
//!
//! The most obvious case where you need the local environment is
//! when the trait reference includes parameter types. For example,
//! consider the following function:
//!
//! impl<T> Vec<T> {
//! fn foo(x: T)
//! where T : Foo
//! { ... }
//!
//! fn bar(x: T)
//! { ... }
//! }
//!
//! If there is an obligation `T : Foo`, or `int : Bar<T>`, or whatever,
//! clearly the results from `foo` and `bar` are potentially different,
//! since the set of where clauses in scope are different.
//!
//! ### Trait references with unbound variables when where clauses are in scope
//!
//! There is another less obvious interaction which involves unbound variables
//! where *only* where clauses are in scope (no impls). This manifested as
//! issue #18209 (`run-pass/trait-cache-issue-18209.rs`). Consider
//! this snippet:
//!
//! ```
//! pub trait Foo {
//! fn load_from() -> Box<Self>;
//! fn load() -> Box<Self> {
//! Foo::load_from()
//! }
//! }
//! ```
//!
//! The default method will incur an obligation `$0 : Foo` from the call
//! to `load_from`. If there are no impls, this can be eagerly resolved to
//! `VtableParam(Self : Foo)` and cached. Because the trait reference
//! doesn't involve any parameters types (only the resolution does), this
//! result was stored in the global cache, causing later calls to
//! `Foo::load_from()` to get nonsense.
//!
//! To fix this, we always use the local cache if there are unbound
//! variables and where clauses in scope. This is more conservative than
//! necessary as far as I can tell. However, it still seems to be a simple
//! rule and I observe ~99% hit rate on rustc, so it doesn't seem to hurt
//! us in particular.
//!
//! Here is an example of the kind of subtle case that I would be worried
//! about with a more complex rule (although this particular case works
//! out ok). Imagine the trait reference doesn't directly reference a
//! where clause, but the where clause plays a role in the winnowing
//! phase. Something like this:
//!
//! ```
//! pub trait Foo<T> { ... }
//! pub trait Bar { ... }
//! impl<U,T:Bar> Foo<U> for T { ... } // Impl A
//! impl Foo<char> for uint { ... } // Impl B
//! ```
//!
//! Now, in some function, we have no where clauses in scope, and we have
//! an obligation `$1 : Foo<$0>`. We might then conclude that `$0=char`
//! and `$1=uint`: this is because for impl A to apply, `uint:Bar` would
//! have to hold, and we know it does not or else the coherence check
//! would have failed. So we might enter into our global cache: `$1 :
//! Foo<$0> => Impl B`. Then we come along in a different scope, where a
//! generic type `A` is around with the bound `A:Bar`. Now suddenly the
//! impl is viable.
//!
//! The flaw in this imaginary DOOMSDAY SCENARIO is that we would not
//! currently conclude that `$1 : Foo<$0>` implies that `$0 == uint` and
//! `$1 == char`, even though it is true that (absent type parameters)
//! there is no other type the user could enter. However, it is not
//! *completely* implausible that we *could* draw this conclusion in the
//! future; we wouldn't have to guess types, in particular, we could be
//! led by the impls.
/*!
# TRAIT RESOLUTION
This document describes the general process and points out some non-obvious
things.
## Major concepts
Trait resolution is the process of pairing up an impl with each
reference to a trait. So, for example, if there is a generic function like:
fn clone_slice<T:Clone>(x: &[T]) -> Vec<T> { ... }
and then a call to that function:
let v: Vec<int> = clone_slice([1, 2, 3].as_slice())
it is the job of trait resolution to figure out (in which case)
whether there exists an impl of `int : Clone`
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
provide an impl. To see what I mean, consider the body of `clone_slice`:
fn clone_slice<T:Clone>(x: &[T]) -> Vec<T> {
let mut v = Vec::new();
for e in x.iter() {
v.push((*e).clone()); // (*)
}
}
The line marked `(*)` is only legal if `T` (the type of `*e`)
implements the `Clone` trait. Naturally, since we don't know what `T`
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 use the term *obligation* to refer to a trait reference in need of
an impl.
## Overview
Trait resolution consists of three major parts:
- SELECTION: Deciding how to resolve a specific obligation. For
example, selection might decide that a specific obligation can be
resolved by employing an impl which matches the self type, or by
using a parameter bound. In the case of an impl, Selecting one
obligation can create *nested obligations* because of where clauses
on the impl itself. It may also require evaluating those nested
obligations to resolve ambiguities.
- FULFILLMENT: The fulfillment code is what tracks that obligations
are completely fulfilled. Basically it is a worklist of obligations
to be selected: once selection is successful, the obligation is
removed from the worklist and any nested obligations are enqueued.
- COHERENCE: The coherence checks are intended to ensure that there
are never overlapping impls, where two impls could be used with
equal precedence.
## Selection
Selection is the process of deciding whether an obligation can be
resolved and, if so, how it is to be resolved (via impl, where clause, etc).
The main interface is the `select()` function, which takes an obligation
and returns a `SelectionResult`. There are three possible outcomes:
- `Ok(Some(selection))` -- yes, the obligation can be resolved, and
`selection` indicates how. If the impl was resolved via an impl,
then `selection` may also indicate nested obligations that are required
by the impl.
- `Ok(None)` -- we are not yet sure whether the obligation can be
resolved or not. This happens most commonly when the obligation
contains unbound type variables.
- `Err(err)` -- the obligation definitely cannot be resolved due to a
type error, or because there are no impls that could possibly apply,
etc.
The basic algorithm for selection is broken into two big phases:
candidate assembly and confirmation.
### Candidate assembly
Searches for impls/where-clauses/etc that might
possibly be used to satisfy the obligation. Each of those is called
a candidate. To avoid ambiguity, we want to find exactly one
candidate that is definitively applicable. In some cases, we may not
know whether an impl/where-clause applies or not -- this occurs when
the obligation contains unbound inference variables.
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
is try and unify the type parameters. (In particular, we ignore any
nested where clauses.) Presuming that this unification succeeds, the
impl is added as a candidate.
Once this first pass is done, we can examine the set of candidates. If
it is a singleton set, then we are done: this is the only impl in
scope that could possibly apply. Otherwise, we can winnow down the set
of candidates by using where clauses and other conditions. If this
reduced set yields a single, unambiguous entry, we're good to go,
otherwise the result is considered ambiguous.
#### The basic process: Inferring based on the impls we see
This process is easier if we work through some examples. Consider
the following trait:
```
trait Convert<Target> {
fn convert(&self) -> Target;
}
```
This trait just has one method. It's about as simple as it gets. It
converts from the (implicit) `Self` type to the `Target` type. If we
wanted to permit conversion between `int` and `uint`, we might
implement `Convert` like so:
```rust
impl Convert<uint> for int { ... } // int -> uint
impl Convert<int> for uint { ... } // uint -> uint
```
Now imagine there is some code like the following:
```rust
let x: int = ...;
let y = x.convert();
```
The call to convert will generate a trait reference `Convert<$Y> for
int`, 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
that only one remains: `Convert<uint> for int`. Therefore, we can
select this impl, which will cause the type of `$Y` to be unified to
`uint`. (Note that while assembling candidates, we do the initial
unifications in a transaction, so that they don't affect one another.)
There are tests to this effect in src/test/run-pass:
traits-multidispatch-infer-convert-source-and-target.rs
traits-multidispatch-infer-convert-target.rs
#### Winnowing: Resolving ambiguities
But what happens if there are multiple impls where all the types
unify? Consider this example:
```rust
trait Get {
fn get(&self) -> Self;
}
impl<T:Copy> Get for T {
fn get(&self) -> T { *self }
}
impl<T:Get> Get for Box<T> {
fn get(&self) -> Box<T> { box get_it(&**self) }
}
```
What happens when we invoke `get_it(&box 1_u16)`, for example? In this
case, the `Self` type is `Box<u16>` -- that unifies with both impls,
because the first applies to all types, and the second to all
boxes. In the olden days we'd have called this ambiguous. But what we
do now is do a second *winnowing* pass that considers where clauses
and attempts to remove candidates -- in this case, the first impl only
applies if `Box<u16> : Copy`, which doesn't hold. After winnowing,
then, we are left with just one candidate, so we can proceed. There is
a test of this in `src/test/run-pass/traits-conditional-dispatch.rs`.
#### Matching
The subroutines that decide whether a particular impl/where-clause/etc
applies to a particular obligation. 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.
#### Lifetimes and selection
Because of how that 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).
#### Where clauses
Besides an impl, the other major way to resolve an obligation is via a
where clause. The selection process is always given a *parameter
environment* which contains a list of where clauses, which are
basically obligations that can assume are satisfiable. We will iterate
over that list and check whether our current obligation can be found
in that list, and if so it is considered satisfied. More precisely, we
want to check whether there is a where-clause obligation that is for
the same trait (or some subtrait) and for which the self types match,
using the definition of *matching* given above.
Consider this simple example:
trait A1 { ... }
trait A2 : A1 { ... }
trait B { ... }
fn foo<X:A2+B> { ... }
Clearly we can use methods offered by `A1`, `A2`, or `B` within the
body of `foo`. In each case, that will incur an obligation like `X :
A1` or `X : A2`. The parameter environment will contain two
where-clauses, `X : A2` and `X : B`. For each obligation, then, we
search this list of where-clauses. To resolve an obligation `X:A1`,
we would note that `X:A2` implies that `X:A1`.
### Confirmation
Confirmation unifies the output type parameters of the trait with the
values found in the obligation, possibly yielding a type error. If we
return to our example of the `Convert` trait from the previous
section, confirmation is where an error would be reported, because the
impl specified that `T` would be `uint`, but the obligation reported
`char`. Hence the result of selection would be an error.
### Selection during translation
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. In this case, we do not consider any
where-clauses to be in scope. We know that therefore each resolution
will resolve to a particular impl.
One interesting twist has to do with nested obligations. In general, in trans,
we only need to do a "shallow" selection for an obligation. That is, we wish to
identify which impl applies, but we do not (yet) need to decide how to select
any nested obligations. Nonetheless, we *do* currently do a complete resolution,
and that is because it can sometimes inform the results of type inference. That is,
we do not have the full substitutions in terms of the type varibales of the impl available
to us, so we must run trait selection to figure everything out.
Here is an example:
trait Foo { ... }
impl<U,T:Bar<U>> Foo for Vec<T> { ... }
impl Bar<uint> for int { ... }
After one shallow round of selection for an obligation like `Vec<int>
: Foo`, we would know which impl we want, and we would know that
`T=int`, but we do not know the type of `U`. We must select the
nested obligation `int : Bar<U>` to find out that `U=uint`.
It would be good to only do *just as much* nested resolution as
necessary. Currently, though, we just do a full resolution.
## Method matching
Method dispach follows a slightly different path than normal trait
selection. This is because it must account for the transformed self
type of the receiver and various other complications. The procedure is
described in `select.rs` in the "METHOD MATCHING" section.
# Caching and subtle considerations therewith
In general we attempt to cache the results of trait selection. This
is a somewhat complex process. Part of the reason for this is that we
want to be able to cache results even when all the types in the trait
reference are not fully known. In that case, it may happen that the
trait selection process is also influencing type variables, so we have
to be able to not only cache the *result* of the selection process,
but *replay* its effects on the type variables.
## An example
The high-level idea of how the cache works is that we first replace
all unbound inference variables with skolemized versions. Therefore,
if we had a trait reference `uint : Foo<$1>`, where `$n` is an unbound
inference variable, we might replace it with `uint : Foo<%0>`, where
`%n` is a skolemized type. We would then look this up in the cache.
If we found a hit, the hit would tell us the immediate next step to
take in the selection process: i.e., apply impl #22, or apply where
clause `X : Foo<Y>`. Let's say in this case there is no hit.
Therefore, we search through impls and where clauses and so forth, and
we come to the conclusion that the only possible impl is this one,
with def-id 22:
impl Foo<int> for uint { ... } // Impl #22
We would then record in the cache `uint : Foo<%0> ==>
ImplCandidate(22)`. Next we would confirm `ImplCandidate(22)`, which
would (as a side-effect) unify `$1` with `int`.
Now, at some later time, we might come along and see a `uint :
Foo<$3>`. When skolemized, this would yield `uint : Foo<%0>`, just as
before, and hence the cache lookup would succeed, yielding
`ImplCandidate(22)`. We would confirm `ImplCandidate(22)` which would
(as a side-effect) unify `$3` with `int`.
## Where clauses and the local vs global cache
One subtle interaction is that the results of trait lookup will vary
depending on what where clauses are in scope. Therefore, we actually
have *two* caches, a local and a global cache. The local cache is
attached to the `ParameterEnvironment` and the global cache attached
to the `tcx`. We use the local cache whenever the result might depend
on the where clauses that are in scope. The determination of which
cache to use is done by the method `pick_candidate_cache` in
`select.rs`.
There are two cases where we currently use the local cache. The
current rules are probably more conservative than necessary.
### Trait references that involve parameter types
The most obvious case where you need the local environment is
when the trait reference includes parameter types. For example,
consider the following function:
impl<T> Vec<T> {
fn foo(x: T)
where T : Foo
{ ... }
fn bar(x: T)
{ ... }
}
If there is an obligation `T : Foo`, or `int : Bar<T>`, or whatever,
clearly the results from `foo` and `bar` are potentially different,
since the set of where clauses in scope are different.
### Trait references with unbound variables when where clauses are in scope
There is another less obvious interaction which involves unbound variables
where *only* where clauses are in scope (no impls). This manifested as
issue #18209 (`run-pass/trait-cache-issue-18209.rs`). Consider
this snippet:
```
pub trait Foo {
fn load_from() -> Box<Self>;
fn load() -> Box<Self> {
Foo::load_from()
}
}
```
The default method will incur an obligation `$0 : Foo` from the call
to `load_from`. If there are no impls, this can be eagerly resolved to
`VtableParam(Self : Foo)` and cached. Because the trait reference
doesn't involve any parameters types (only the resolution does), this
result was stored in the global cache, causing later calls to
`Foo::load_from()` to get nonsense.
To fix this, we always use the local cache if there are unbound
variables and where clauses in scope. This is more conservative than
necessary as far as I can tell. However, it still seems to be a simple
rule and I observe ~99% hit rate on rustc, so it doesn't seem to hurt
us in particular.
Here is an example of the kind of subtle case that I would be worried
about with a more complex rule (although this particular case works
out ok). Imagine the trait reference doesn't directly reference a
where clause, but the where clause plays a role in the winnowing
phase. Something like this:
```
pub trait Foo<T> { ... }
pub trait Bar { ... }
impl<U,T:Bar> Foo<U> for T { ... } // Impl A
impl Foo<char> for uint { ... } // Impl B
```
Now, in some function, we have no where clauses in scope, and we have
an obligation `$1 : Foo<$0>`. We might then conclude that `$0=char`
and `$1=uint`: this is because for impl A to apply, `uint:Bar` would
have to hold, and we know it does not or else the coherence check
would have failed. So we might enter into our global cache: `$1 :
Foo<$0> => Impl B`. Then we come along in a different scope, where a
generic type `A` is around with the bound `A:Bar`. Now suddenly the
impl is viable.
The flaw in this imaginary DOOMSDAY SCENARIO is that we would not
currently conclude that `$1 : Foo<$0>` implies that `$0 == uint` and
`$1 == char`, even though it is true that (absent type parameters)
there is no other type the user could enter. However, it is not
*completely* implausible that we *could* draw this conclusion in the
future; we wouldn't have to guess types, in particular, we could be
led by the impls.
*/