Associated Types and Advanced Traits

Trait Sealing Patterns

A sealed trait is a public trait that downstream users can use as a bound or call methods on, but cannot implement for their own types outside the defining crate.

How the Supertrait Sealing Pattern Works

Rust does not have a dedicated sealed keyword on stable, so sealing is achieved using visibility rules:

  1. Declare a private module (mod private) containing a pub trait Sealed {}.
  2. Make private::Sealed a supertrait of your public trait (pub trait MyTrait: private::Sealed).
  3. Implement private::Sealed only for the fixed set of types in your crate that are allowed to implement MyTrait.
Crate Boundary
+-------------------------------------------------------+
|  mod private {                                        |
|      pub trait Sealed {}    <-- unreachable outside   |
|  }                                                    |
|                                                       |
|  pub trait ByteOrder: private::Sealed { ... }         |
|           ▲                                           |
|           └── Public to callers, but requires Sealed  |
+-------------------------------------------------------+

Because mod private is not exported (pub), external crates cannot name private::Sealed and therefore cannot satisfy the supertrait requirement needed to implement ByteOrder.

Complete Sealed Trait Example

mod private {
    pub trait Sealed {}
}

/// Public trait that external crates can use in generic bounds,
/// but cannot implement for their own types.
pub trait Endian: private::Sealed {
    const NAME: &'static str;
    fn read_u16(bytes: [u8; 2]) -> u16;
}

pub struct BigEndian;
pub struct LittleEndian;

impl private::Sealed for BigEndian {}
impl private::Sealed for LittleEndian {}

impl Endian for BigEndian {
    const NAME: &'static str = "BigEndian";
    fn read_u16(bytes: [u8; 2]) -> u16 {
        u16::from_be_bytes(bytes)
    }
}

impl Endian for LittleEndian {
    const NAME: &'static str = "LittleEndian";
    fn read_u16(bytes: [u8; 2]) -> u16 {
        u16::from_le_bytes(bytes)
    }
}

fn decode_header<E: Endian>(raw: [u8; 2]) {
    println!("{}: 0x{:04X}", E::NAME, E::read_u16(raw));
}

fn main() {
    let raw = [0x12, 0x34];
    decode_header::<BigEndian>(raw);
    decode_header::<LittleEndian>(raw);
}
BigEndian: 0x1234
LittleEndian: 0x3412

Method-Level Sealing with a Private Token Parameter

If you want external crates to be able to call a trait method, or if you want a trait to remain partially implementable while locking specific internal methods, require a private zero-sized Token type in the method signature:

mod internal {
    pub struct SealToken;
}

pub trait Driver {
    // External callers cannot construct `internal::SealToken`:
    fn register_internal(&self, _token: internal::SealToken);
}

Benefits of Sealing Public Traits

Benefit Explanation
Non-breaking method additions Adding a new required method to a sealed trait is not a SemVer-breaking change because no downstream crate can have an impl block that breaks
Exhaustive finite type sets Guarantees a type parameter <T: SealedTrait> can only ever be instantiated with the known closed set of types
Memory safety in unsafe code Allows safe functions to rely on invariants of SealedTrait implementations without requiring unsafe trait in the public API