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:
- Declare a private module (
mod private) containing apub trait Sealed {}. - Make
private::Sealeda supertrait of your public trait (pub trait MyTrait: private::Sealed). - Implement
private::Sealedonly for the fixed set of types in your crate that are allowed to implementMyTrait.
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 |