Type System Fundamentals

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.