Nominal Types
Rust's user-defined types (struct, enum, and union) and traits are nominal: two types are considered equal if and only if they have the exact same declared type definition (name/path), even if their fields and memory layouts are 100% identical.
Nominal Typing vs. Structural Typing
| Typing Discipline | Rule for Type Compatibility | Rust Constructs Using It |
|---|---|---|
| Nominal typing | Compatible only if they refer to the same named struct, enum, or trait declaration |
struct, enum, union, trait |
| Structural typing | Compatible if their component types match by shape and position | Tuples (A, B), arrays [T; N], slices [T], references &T, function pointers fn(A) -> B |
Nominal (Distinct Types!): Structural (Identical Type!):
struct Meters(f64); let a: (f64, f64) = (1.0, 2.0);
struct Seconds(f64); let b: (f64, f64) = a; // Same structural tuple type
// `Meters` != `Seconds` (`E0308`)
Prevents Accidental Mixing of Domain Quantities (E0308)
Because structs are nominal, two structs with identical fields (x: f64, y: f64) cannot be assigned to each other or passed interchangeably:
// ❌ does not compile
struct WorldCoord {
x: f64,
y: f64,
}
struct ScreenPixel {
x: f64,
y: f64,
}
fn draw_at(px: ScreenPixel) {
println!("Drawing at ({}, {})", px.x, px.y);
}
fn main() {
let world = WorldCoord { x: 10.0, y: 20.0 };
draw_at(world); // Error: expected `ScreenPixel`, found `WorldCoord`
}
error[E0308]: mismatched types
--> src/main.rs:19:13
|
19 | draw_at(world);
| ------- ^^^^^ expected `ScreenPixel`, found `WorldCoord`
| |
| arguments to this function are incorrect
Nominal Trait Satisfaction (No Accidental "Duck Typing")
In structural / duck-typed languages (such as Go interfaces or TypeScript interfaces), any type that happens to have a method .read() automatically satisfies a Reader interface—even if the method's semantics are unrelated (such as Book::read() vs. Socket::read()).
In Rust, traits are nominal: a type T implements a trait Trait only when an explicit impl Trait for T block is written (with the sole exception of compiler auto traits like Send and Sync). Defining an inherent method with the same name and signature as a trait method never accidentally opts a type into that trait.