Course contentsShow
← Rust
Lesson 487 of 1,9105. Type System and TraitsFree lesson

Nominal Typing versus Structural Typing

Contrast Rust's explicit nominal type system with duck-typed alternatives.

In this lesson, you will learn why Rust forces you to explicitly name your types and how this differs from "duck typing" systems.

What is Nominal Typing?

Rust uses a nominal type system. This means that a type is defined by its name, not by its internal structure. Even if two struct definitions have the exact same fields, Rust considers them entirely different types. If you try to pass one where the other is expected, the compiler will stop you.

Consider this example:

struct Dog { age: u32 }
struct Cat { age: u32 }

fn play_with_dog(dog: Dog) {}

fn main() {
    let kitty = Cat { age: 3 };
    // play_with_dog(kitty); // Error: expected Dog, found Cat
}

Even though Dog and Cat share the exact same memory layout and field names, they are not interchangeable. This is "nominal" because the name is the source of truth for the type system.

The Alternative: Structural Typing

In contrast, some languages (like TypeScript or Go, to an extent) use structural typing. In those systems, if two types have the same "shape"—the same fields and methods—they are considered the same type. This is often called "duck typing": if it looks like a duck and quacks like a duck, it’s a duck.

Why Rust chooses Nominal Typing

By requiring explicit names, Rust provides several benefits:

  1. Clarity: You always know exactly what a function expects. You aren't accidentally passing a Cat into a function meant for a Dog just because both happen to have an age field.
  2. Intent: You define the domain model clearly. You are telling the compiler, "This is a Dog," which carries semantic meaning beyond just the data it holds.
  3. Refactoring Safety: If you add a field to Dog, you won't accidentally break parts of your code that were expecting a Cat.

In Rust, if you do want to treat different types as if they share common behavior, you don't use structural matching; you use Traits to define shared capabilities explicitly.

Key Takeaway: Rust uses nominal typing, meaning types are defined by their identity and name rather than their structure, ensuring high levels of safety and intent at the cost of strictness.