|
| 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 | + |
0 commit comments