Traits

Trait Inheritance-Like Bounds

Rust does not have class inheritance where a subclass inherits fields and memory layout from a base class. Instead, Rust models hierarchical capability relationships through trait inheritance-like bounds (trait Subtrait: BaseTrait), where a subtrait declares that every implementing type must also implement one or more base traits.

How Subtrait Bounds Differ from Object-Oriented Inheritance

OOP Class Inheritance (Java/C++)      Rust Trait Hierarchy (Subtrait Bounds)
+------------------------------+      +------------------------------------+
| class Base { int id; }       |      | trait Identifiable { fn id(&self);}|
| class Derived : Base { ... } |      | trait Auditable: Identifiable { ..}|
+------------------------------+      +------------------------------------+
- Shares memory layout / fields       - Zero shared fields or memory layout
- Implicit subtyping                  - Explicit prerequisite contract on Self

In Rust, writing trait Auditable: Identifiable is shorthand for placing a where Self: Identifiable constraint on the trait definition itself.

Implementing a Hierarchy of Traits

Each trait in the hierarchy is still implemented in its own separate impl block for a concrete type:

trait Named {
    fn name(&self) -> &str;
}

trait Versioned: Named {
    fn version(&self) -> (u32, u32, u32);

    fn full_identifier(&self) -> String {
        let (maj, min, pat) = self.version();
        // Can call `self.name()` directly because `Self: Named` is guaranteed:
        format!("{} v{}.{}.{}", self.name(), maj, min, pat)
    }
}

struct Package {
    pkg_name: String,
    semver: (u32, u32, u32),
}

// 1. Implement the prerequisite trait:
impl Named for Package {
    fn name(&self) -> &str {
        &self.pkg_name
    }
}

// 2. Implement the subtrait:
impl Versioned for Package {
    fn version(&self) -> (u32, u32, u32) {
        self.semver
    }
}

fn print_package_info<T: Versioned>(item: &T) {
    // A generic function bounded only by `T: Versioned` can call methods from BOTH traits:
    println!("Name: {}", item.name());
    println!("Full: {}", item.full_identifier());
}

fn main() {
    let pkg = Package {
        pkg_name: String::from("serde"),
        semver: (1, 0, 210),
    };
    print_package_info(&pkg);
}
Name: serde
Full: serde v1.0.210

Trait Alias Pattern via Empty Subtraits + Blanket Implementations

Because stable Rust does not yet have a trait Alias = TraitA + TraitB; keyword, libraries often combine a subtrait bound with a blanket implementation to create a trait alias for a frequently repeated group of bounds:

use std::fmt::{Debug, Display};

// 1. Declare an empty subtrait requiring all component traits:
trait PrimKey: Copy + Ord + std::hash::Hash + Debug + Display {}

// 2. Blanket-implement `PrimKey` automatically for EVERY type satisfying those bounds:
impl<T> PrimKey for T where T: Copy + Ord + std::hash::Hash + Debug + Display {}

fn log_sorted_keys<K: PrimKey>(mut keys: Vec<K>) {
    keys.sort();
    for k in keys {
        println!("Key: {k} ({k:?})");
    }
}

fn main() {
    log_sorted_keys(vec![42_u32, 7, 19]);
    log_sorted_keys(vec!["beta", "alpha", "gamma"]);
}
Key: 7 (7)
Key: 19 (19)
Key: 42 (42)
Key: alpha ("alpha")
Key: beta ("beta")
Key: gamma ("gamma")
OOP Inheritance Feature Rust Equivalent
Interface extends interface (interface B extends A) trait B: A (supertrait bound)
Shared default behavior across implementors Default method implementations inside trait
Reusing fields from another struct Struct composition (embedding inner: BaseStruct as a field)
Delegating methods to an inner field Forwarding methods in impl Trait for Outer (or Deref for smart pointers)