Type Unification
Type unification is the algorithm used by rustc's type checker (rustc_infer) to solve systems of type equality constraints ($T_1 = T_2$), substituting unknown inference variables (?T, ?Int, ?Float) with concrete types until every expression has a single, consistent type—or reporting a type error if two incompatible types are forced to unify.
How Unification Solves Inference Variables Step by Step
When type-checking a function body, the compiler assigns a fresh inference variable (?0, ?1, ...) to every unannotated binding and generic type parameter, collects equality constraints from operations, and unifies them:
Code:
let mut v = Vec::new(); // 1. v: Vec<?0>
let x = 42; // 2. x: ?1 (integer variable)
v.push(x); // 3. Unifies Vec<?0>::push(?0) with x: ?1 ==> ?0 = ?1
let slice: &[u64] = &v; // 4. Unifies &Vec<?0> -> &[?0] with &[u64] ==> ?0 = u64
// 5. By transitivity (?1 = ?0 = u64), both `x` and `v` resolve!
// v: Vec<u64>, x: u64
fn main() {
let mut v = Vec::new();
let x = 42;
v.push(x);
// Unifying `&v` with `&[u64]` resolves `?0 = u64` and `x: u64`:
let slice: &[u64] = &v;
println!("Resolved slice: {slice:?} (elem size = {} bytes)", size_of_val(&x));
}
Resolved slice: [42] (elem size = 8 bytes)
Structural Decomposition During Unification
When unifying two compound types (such as Result<?A, String> and Result<i32, ?B>), the unification engine checks that the outer type constructors match (Result == Result) and recursively unifies their corresponding type arguments:
?Aunifies withi32($\implies ?A = \text{i32}$)Stringunifies with?B($\implies ?B = \text{String}$)- Resulting unified type:
Result<i32, String>
fn operation(succeed: bool) -> Result<i32, String> {
if succeed {
// `Ok(10)` has type `Result<i32, ?E>` -> unifies with return type (`?E = String`)
Ok(10)
} else {
// `Err(...)` has type `Result<?T, String>` -> unifies with return type (`?T = i32`)
Err(String::from("failed"))
}
}
fn main() {
println!("{:?}, {:?}", operation(true), operation(false));
}
Ok(10), Err("failed")
Unification Failure and the "Occurs Check" (Infinite Types E0308)
Unification fails in two ways:
- Constructor mismatch (
E0308): Attempting to unify two different concrete types (such as?0 = u32from one line and?0 = &strfrom another line). - Occurs check (infinite type): Attempting to unify an inference variable
?Twith a type that contains?Titself (such as?T = Vec<?T>), which would require an infinitely nested typeVec<Vec<Vec<...>>>.