Sealed Traits
A sealed trait is a pub trait that downstream users can use as a trait bound or call methods on, but cannot implement for their own types outside the defining crate. Sealing a trait preserves the library author's freedom to add new required methods or associated types in minor releases without breaking semver compatibility, and guarantees that a closed set of type-state or marker implementations remains exhaustive.
How the Supertrait Sealing Pattern Works
Rust does not have a built-in sealed keyword. Instead, a trait is sealed by making it require a supertrait defined inside a private module (mod private { pub trait Sealed {} }):
Crate Root (`pub trait BytePrimitive: private::Sealed`)
|
+-- Public to external callers:
| `pub trait BytePrimitive` (External crates CAN bound `T: BytePrimitive`)
|
+-- Private submodule (`mod private` — NOT exported with `pub mod`!):
`pub trait Sealed {}` (External crates CANNOT name `private::Sealed`,
so they CANNOT write `impl Sealed for MyType`!)
Because implementing BytePrimitive requires also implementing its supertrait private::Sealed (E0277), and mod private is inaccessible outside the crate, only types inside the defining crate can implement BytePrimitive.
mod private {
pub trait Sealed {}
}
/// A trait that external callers can use in generic bounds,
/// but cannot implement for new types outside this module.
pub trait EndianPrimitive: private::Sealed + Copy {
fn to_network_bytes(self) -> Vec<u8>;
}
impl private::Sealed for u16 {}
impl EndianPrimitive for u16 {
fn to_network_bytes(self) -> Vec<u8> {
self.to_be_bytes().to_vec()
}
}
impl private::Sealed for u32 {}
impl EndianPrimitive for u32 {
fn to_network_bytes(self) -> Vec<u8> {
self.to_be_bytes().to_vec()
}
}
pub fn encode_value<T: EndianPrimitive>(val: T) -> Vec<u8> {
val.to_network_bytes()
}
fn main() {
println!("u16: {:02x?}", encode_value(0x1234_u16));
println!("u32: {:02x?}", encode_value(0x1234_5678_u32));
}
u16: [12, 34]
u32: [12, 34, 56, 78]
Method-Level Sealing with a Private Token Parameter
An alternative way to seal individual methods (or the entire trait without a supertrait) is requiring a private private::Token parameter on a trait method. External crates cannot construct or name private::Token, so they cannot write a valid method implementation:
mod internal {
pub struct SealToken;
}
pub trait StateMarker {
#[doc(hidden)]
fn _seal(token: internal::SealToken);
}
pub struct OpenState;
impl StateMarker for OpenState {
fn _seal(_: internal::SealToken) {}
}
fn verify_state<S: StateMarker>() {
S::_seal(internal::SealToken);
println!("verified sealed state marker");
}
fn main() {
verify_state::<OpenState>();
}
verified sealed state marker
When to Seal a Trait
| Use Case | Why Sealing Helps |
|---|---|
Type-state markers (trait ConnectionState: Sealed) |
Guarantees callers cannot invent fake states like struct SpoofedAuthenticated; |
Overloaded parameter traits (str::Pattern, SliceIndex) |
Allows standard/library functions to accept &str, char, or [char; N] via one trait without committing to public stabilization of internal methods |
| Evolving public traits | Adding a new non-default method to an unsealed pub trait is a major semver-breaking change; adding a method to a sealed trait is non-breaking |