8.3 Type Equivalence, Compatibility, Coercion, and Casts
Once a language has many types, the compiler must answer a more subtle question than “what type is this expression?” It must also answer, “when may one type stand in for another?” That is where equivalence, compatibility, coercion, and casts enter.
These words are related but not interchangeable.
| concept | asks | typical example |
|---|---|---|
| equivalence | are these types considered the same? | two aliases of int |
| compatibility | may a value of one type be used where another is expected? | int used where float is accepted |
| implicit coercion | may the compiler insert a conversion silently? | widening int -> float |
| explicit cast | may the programmer force a conversion in source? | int(3.9) |
Equivalence Is Stronger Than Compatibility
Two types can be compatible without being equivalent. For example, a language may allow int to flow into float positions because widening is low-risk and unsurprising. But that does not mean int and float are the same type.
This distinction matters because type errors become easier to explain:
- “not equivalent” means the language considers the two types different.
- “not compatible” means the language does not permit this use in this context.
- “requires explicit cast” means the language considers the step risky enough that the programmer must spell it out.
Implicit Coercions Must Be Conservative
An implicit coercion is a promise that the compiler can change the program representation in a way the language considers unsurprising. That is a powerful promise, so it should be used carefully.
Good candidates for implicit coercion are usually:
- common,
- low-risk,
- semantically obvious,
- and unlikely to hide bugs.
That is why widening int -> float is often tolerated while narrowing float -> int is not. The latter may lose precision, truncate information, or surprise the reader.
The same caution applies even more strongly to user-defined nominal types. If UserId silently becomes int, the compiler has thrown away a semantic boundary that the type system may have been designed to protect.
Casts Make Risk Explicit
An explicit cast is not a proof that the conversion is safe. It is a source-level statement that says, “I know this step is not ordinary, and I am taking responsibility for it.”
That responsibility can exist for different reasons:
| cast kind | main danger |
|---|---|
| narrowing numeric cast | precision or range loss |
| nominal-to-primitive escape | semantic boundary erased |
| reinterpret-style cast | type-system guarantees bypassed |
This is why reviews and diagnostics should treat casts differently from silent coercions. A silent coercion is a language-wide policy. A cast is a local exception request.
Engineering Rule of Thumb
If a conversion can plausibly change program meaning, lose information, or erase a domain boundary, force the programmer to write it explicitly or reject it altogether.
Worked Example
Suppose a language has:
type UserId = int
type Meters = floatEven if those are represented as numbers underneath, the compiler may still reject assigning UserId into Meters or vice versa. The rejection is not about storage size. It is about meaning. The type checker is guarding against semantic confusion, not just byte-level mismatch.