| # Variance of type and lifetime parameters | |
| For a more general background on variance, see the [background] appendix. | |
| [background]: ./appendix/background.md | |
| During type checking, we must infer the variance of type and lifetime parameters. | |
| The algorithm is taken from Section 4 of the paper ["Taming the | |
| Wildcards: Combining Definition- and Use-Site Variance"][pldi11] published in | |
| PLDI'11 and written by Altidor et al., and hereafter referred to as The Paper. | |
| [pldi11]: https://people.cs.umass.edu/~yannis/variance-extended2011.pdf | |
| This inference is explicitly designed *not* to consider the uses of types within code. | |
| To determine the variance of type parameters defined on type `X`, | |
| we only consider the definition of the type `X` and the definitions of any types it references. | |
| We only infer variance for type parameters found on *data types* like structs and enums. | |
| In these cases, there is a fairly straightforward explanation for what variance means. | |
| The variance of the type or lifetime parameters defines whether `T<A>` is a subtype of `T<B>` | |
| (`T<'a>` and `T<'b>` respectively) | |
| based on the relationship of `A` and `B` (`'a` and `'b` respectively). | |
| We do not infer variance for type parameters found on traits, functions, or impls. | |
| Variance on trait parameters can indeed make sense | |
| (and we used to compute it) but it is actually rather subtle in | |
| meaning and not that useful in practice, so we removed it. | |
| See the [addendum] for some details. | |
| Variances on function/impl parameters, on the other hand, | |
| doesn't make sense because these parameters are instantiated and then forgotten; | |
| they don't persist in types or compiled byproducts. | |
| [addendum]: #addendum | |
| > **Notation** | |
| > | |
| > We use the notation of The Paper throughout this chapter: | |
| > | |
| > - `+` is _covariance_. | |
| > - `-` is _contravariance_. | |
| > - `*` is _bivariance_. | |
| > - `o` is _invariance_. | |
| ## The algorithm | |
| The basic idea is quite straightforward. | |
| We iterate over the types defined and, for each use of a type parameter `X`, accumulate a | |
| constraint indicating that the variance of `X` must be valid for the variance of that use site. | |
| We then iteratively refine the variance of `X` until all constraints are met. | |
| There is *always* a solution, because at | |
| the limit we can declare all type parameters to be invariant and all constraints will be satisfied. | |
| As a simple example, consider: | |
| ```rust,ignore | |
| enum Option<A> { Some(A), None } | |
| enum OptionalFn<B> { Some(|B|), None } | |
| enum OptionalMap<C> { Some(|C| -> C), None } | |
| ``` | |
| Here, we will generate the constraints: | |
| ```text | |
| 1. V(A) <= + | |
| 2. V(B) <= - | |
| 3. V(C) <= + | |
| 4. V(C) <= - | |
| ``` | |
| These indicate that (1) the variance of A must be at most covariant; | |
| (2) the variance of B must be at most contravariant; and (3, 4) the | |
| variance of C must be at most covariant *and* contravariant. | |
| All of these results are based on a variance lattice defined as follows: | |
| ```text | |
| * Top (bivariant) | |
| - + | |
| o Bottom (invariant) | |
| ``` | |
| Based on this lattice, the solution `V(A)=+`, `V(B)=-`, `V(C)=o` is the optimal solution. | |
| Note that there is always a naive solution which just declares all variables to be invariant. | |
| You may be wondering why fixed-point iteration is required. | |
| The reason is that the variance of a use site may itself be a function of the | |
| variance of other type parameters. | |
| In full generality, our constraints take the form: | |
| ```text | |
| V(X) <= Term | |
| Term := + | - | * | o | V(X) | Term x Term | |
| ``` | |
| Here the notation `V(X)` indicates the variance of a type/region | |
| parameter `X` with respect to its defining class. | |
| `Term x Term` represents the "variance transform" as defined in the paper: | |
| > If the variance of a type variable `X` in type expression `E` is `V2` | |
| and the definition-site variance of the corresponding type parameter | |
| of a class `C` is `V1`, then the variance of `X` in the type expression | |
| `C<E>` is `V3 = V1.xform(V2)`. | |
| ## Constraints | |
| If I have a struct or enum with where clauses: | |
| ```rust,ignore | |
| struct Foo<T: Bar> { ... } | |
| ``` | |
| You might wonder whether the variance of `T` with respect to `Bar` affects the | |
| variance `T` with respect to `Foo`. | |
| I claim no. | |
| The reason: assume that `T` is invariant with respect to `Bar` but covariant with respect to `Foo`. | |
| And then we have a `Foo<X>` that is upcast to `Foo<Y>`, where `X <: Y`. | |
| However, while `X : Bar`, `Y : Bar` does not hold. | |
| In that case, the upcast will be illegal, | |
| but not because of a variance failure, but rather because the target type | |
| `Foo<Y>` is itself just not well-formed. | |
| Basically, we get to assume well-formedness of all types involved before considering variance. | |
| ### Dependency graph management | |
| Because variance is a whole-crate inference, its dependency graph | |
| can become quite muddled if we are not careful. | |
| To resolve this, we refactor into two queries: | |
| - `crate_variances` computes the variance for all items in the current crate. | |
| - `variances_of` accesses the variance for an individual reading; it | |
| works by requesting `crate_variances` and extracting the relevant data. | |
| If you limit yourself to reading `variances_of`, your code will only | |
| depend then on the inference of that particular item. | |
| Ultimately, this setup relies on the [red-green algorithm][rga]. | |
| In particular, | |
| every variance query effectively depends on all type definitions in the entire | |
| crate (through `crate_variances`), but since most changes will not result in a | |
| change to the actual results from variance inference, the `variances_of` query | |
| will wind up being considered green after it is re-evaluated. | |
| [rga]: ./queries/incremental-compilation.md | |
| <a id="addendum"></a> | |
| ## Addendum: Variance on traits | |
| As mentioned above, we used to permit variance on traits. | |
| This was computed based on the appearance of trait type parameters in | |
| method signatures and was used to represent the compatibility of | |
| vtables in trait objects (and also "virtual" vtables or dictionary in trait bounds). | |
| One complication was that variance for | |
| associated types is less obvious, since they can be projected out | |
| and put to myriad uses, so it's not clear when it is safe to allow | |
| `X<A>::Bar` to vary (or indeed just what that means). | |
| Moreover (as covered below) all inputs on any trait with an associated type had | |
| to be invariant, limiting the applicability. | |
| Finally, the annotations (`MarkerTrait`, `PhantomFn`) needed to ensure that all | |
| trait type parameters had a variance were confusing and annoying for little benefit. | |
| Just for historical reference, I am going to preserve some text indicating how | |
| one could interpret variance and trait matching. | |
| ### Variance and object types | |
| Just as with structs and enums, we can decide the subtyping | |
| relationship between two object types `&Trait<A>` and `&Trait<B>` | |
| based on the relationship of `A` and `B`. | |
| Note that for object types we ignore the `Self` type parameter – it is unknown, and | |
| the nature of dynamic dispatch ensures that we will always call a | |
| function that is expected the appropriate `Self` type. | |
| However, we must be careful with the other type parameters, or else we could | |
| end up calling a function that is expecting one type but provided another. | |
| To see what I mean, consider a trait like so: | |
| ```rust | |
| trait ConvertTo<A> { | |
| fn convertTo(&self) -> A; | |
| } | |
| ``` | |
| Intuitively, If we had one object `O=&ConvertTo<Object>` and another | |
| `S=&ConvertTo<String>`, then `S <: O` because `String <: Object` | |
| (presuming Java-like "string" and "object" types, my go to examples for subtyping). | |
| The actual algorithm would be to compare the | |
| (explicit) type parameters pairwise respecting their variance: here, | |
| the type parameter A is covariant (it appears only in a return | |
| position), and hence we require that `String <: Object`. | |
| You'll note though that we did not consider the binding for the | |
| (implicit) `Self` type parameter: in fact, it is unknown, so that's good. | |
| The reason we can ignore that parameter is precisely because we | |
| don't need to know its value until a call occurs, and at that time (as | |
| you said) the dynamic nature of virtual dispatch means the code we run | |
| will be correct for whatever value `Self` happens to be bound to for | |
| the particular object whose method we called. | |
| `Self` is thus different from `A`, because the caller requires that `A` be known in order to | |
| know the return type of the method `convertTo()`. | |
| (As an aside, we have rules preventing methods where `Self` appears outside of the | |
| receiver position from being called via an object.) | |
| ### Trait variance and vtable resolution | |
| But traits aren't only used with objects. | |
| They're also used when deciding whether a given impl satisfies a given trait bound. | |
| To set the scene here, imagine I had a function: | |
| ```rust,ignore | |
| fn convertAll<A,T:ConvertTo<A>>(v: &[T]) { ... } | |
| ``` | |
| Now imagine that I have an implementation of `ConvertTo` for `Object`: | |
| ```rust,ignore | |
| impl ConvertTo<i32> for Object { ... } | |
| ``` | |
| And I want to call `convertAll` on an array of strings. | |
| Suppose further that for whatever reason I specifically supply the value of | |
| `String` for the type parameter `T`: | |
| ```rust,ignore | |
| let mut vector = vec!["string", ...]; | |
| convertAll::<i32, String>(vector); | |
| ``` | |
| Is this legal? | |
| To put another way, can we apply the `impl` for `Object` to the type `String`? | |
| The answer is yes, but to see why, we have to expand out what will happen: | |
| - `convertAll` will create a pointer to one of the entries in the | |
| vector, which will have type `&String` | |
| - It will then call the impl of `convertTo()` that is intended for use with objects. | |
| This has the type `fn(self: &Object) -> i32`. | |
| It is OK to provide a value for `self` of type `&String` because `&String <: &Object`. | |
| OK, so intuitively we want this to be legal, so let's bring this back | |
| to variance and see whether we are computing the correct result. | |
| We must first figure out how to phrase the question "is an impl for | |
| `Object,i32` usable where an impl for `String,i32` is expected?" | |
| Maybe it's helpful to think of a dictionary-passing implementation of type classes. | |
| In that case, `convertAll()` takes an implicit parameter representing the impl. | |
| In short, we *have* an impl of type: | |
| ```text | |
| V_O = ConvertTo<i32> for Object | |
| ``` | |
| And the function prototype expects an impl of type: | |
| ```text | |
| V_S = ConvertTo<i32> for String | |
| ``` | |
| As with any argument, this is legal if the type of the value given | |
| (`V_O`) is a subtype of the type expected (`V_S`). | |
| So is `V_O <: V_S`? | |
| The answer will depend on the variance of the various parameters. | |
| In this case, because the `Self` parameter is contravariant and `A` is | |
| covariant, it means that: | |
| ```text | |
| V_O <: V_S iff | |
| i32 <: i32 | |
| String <: Object | |
| ``` | |
| These conditions are satisfied and so we are happy. | |
| ### Variance and associated types | |
| Traits with associated types – or at minimum projection | |
| expressions – must be invariant with respect to all of their inputs. | |
| To see why this makes sense, consider what subtyping for a trait reference means: | |
| ```text | |
| <T as Trait> <: <U as Trait> | |
| ``` | |
| It means that if I know that `T as Trait`, I also know that `U as Trait`. | |
| Moreover, if you think of it as dictionary passing style, | |
| it means that a dictionary for `<T as Trait>` is safe to use where | |
| a dictionary for `<U as Trait>` is expected. | |
| The problem is that when you can project types out from `<T as | |
| Trait>`, the relationship to types projected out of `<U as Trait>` | |
| is completely unknown unless `T==U` (see #21726 for more details). | |
| Making `Trait` invariant ensures that this is true. | |
| Another related reason is that if we didn't make traits with | |
| associated types invariant, then projection is no longer a function with a single result. | |
| Consider: | |
| ```rust,ignore | |
| trait Identity { type Out; fn foo(&self); } | |
| impl<T> Identity for T { type Out = T; ... } | |
| ``` | |
| Now if I have `<&'static () as Identity>::Out`, this can be | |
| validly derived as `&'a ()` for any `'a`: | |
| ```text | |
| <&'a () as Identity> <: <&'static () as Identity> | |
| if &'static () < : &'a () -- Identity is contravariant in Self | |
| if 'static : 'a -- Subtyping rules for relations | |
| ``` | |
| This change otoh means that `<'static () as Identity>::Out` is | |
| always `&'static ()` (which might then be upcast to `'a ()`, | |
| separately). | |
| This was helpful in solving #21750. |