#[sealed]Expand description
Seals a trait so that only the listed types can implement it, and every listed type must implement it.
#[sealed(Circle, Square)] // error: Square does not implement Shape
trait Shape {}
struct Circle;
impl Shape for Circle {}
struct Square;
impl Shape for i32 {} // error: i32 is not permitted§Entries
Each entry is a type, and can further say how that type implements the trait.
§Generic traits and types
One rule governs everything in this section:
A bare name is a parameter only if the trait or a for<..> declares it; otherwise it is
whatever concrete type or const is in scope. The three cases below are the three ways an entry
can answer that, and each takes lifetimes, types and const parameters alike.
§Parameters the trait declares
A generic type implementing a generic trait at the same parameters names them as the trait declares them:
#[sealed(Boxed<'a, T>)] // `'a` and `T` are declared by the `Store` trait
trait Store<'a, T> {}
struct Boxed<'t, X>(&'t X);
impl<'t, X> Store<'t, X> for Boxed<'t, X> {}Note which names the entry uses: Boxed declares 't and X, and the entry still writes 'a
and T. A bare name in an entry is read against the trait, never against the type it belongs
to. A const parameter is named the same way, so Row<N> under trait Width<const N: usize>
means every Row.
§One instantiation
An implementor may implement a generic trait at one instantiation rather than generically. The
Entry: Trait<..> syntax says which:
struct Plain;
struct Boxed<T>(pub T);
struct Keyed<T>(pub T);
#[sealed(
Plain: Store<i32>, // implements the trait at one instantiation
Boxed<T>, // the identity mapping needs no annotation
Keyed<T>: Store<Vec<T>>, // generic, but not the identity mapping
)]
trait Store<T> {}
impl Store<i32> for Plain {}
impl<T> Store<T> for Boxed<T> {}
impl<T> Store<Vec<T>> for Keyed<T> {}§Parameters the trait does not declare
A type may be generic over parameters the trait knows nothing about. The entry declares them
itself, with for<..>:
struct Boxed<T>(T);
#[sealed(for<T> Boxed<T>)]
trait Shape {}
impl<T> Shape for Boxed<T> {}Lifetimes work the same way, except that for them the binder is not optional. Left out, the same spelling would mean the trait’s lifetime or every lifetime depending on what the trait happened to call its parameter, so renaming that parameter would quietly change what is sealed:
struct Str<'a>(&'a str);
#[sealed(for<'a> Str<'a>)]
trait Shape {}
impl<'a> Shape for Str<'a> {}Lifetimes, types and const parameters can be declared together, lifetimes first (as in
for<'a, T: Clone, const N: usize>), and each is written exactly as it would be on an impl,
so a const parameter carries its type.
§as Name
Names the entry. The seal itself does not care: it is enumerate that reads
the name, giving each variant the type’s last path segment unless one is written here. Two
entries collide over that in two ways.
Different types whose last segment matches. Here the name settles which is which:
mod a { pub struct Foo; }
mod b { pub struct Foo; }
#[enumerate]
#[sealed(a::Foo as Left, b::Foo as Right)]
trait Shape {}
impl Shape for a::Foo {}
impl Shape for b::Foo {}
fn main() {
let _ = AnyShape::Left(a::Foo); // see enumerate
}The same type listed twice, which is how one type reaches the enum at more than one
instantiation. There the name is not a nicety but required, since both entries would otherwise
be the Plain variant:
struct Plain;
struct Boxed<T>(pub T);
#[enumerate]
#[sealed(Plain: Store<i32>, Plain as PlainF64: Store<f64>, Boxed<T>)]
trait Store<T> {}
impl Store<i32> for Plain {}
impl Store<f64> for Plain {}
impl<T> Store<T> for Boxed<T> {}
fn main() {
// the one type reaching two different enum instantiations
assert!(matches!(Plain.into_enum(), AnyStore::<i32>::Plain(_)));
assert!(matches!(Plain.into_enum(), AnyStore::<f64>::PlainF64(_)));
}The name settles the variant only. The two entries must also pin different arguments, and
some entry (Boxed<T> here) has to mention T. The enum is generic over the parameters
its variants use, not over the trait’s: an enum declaring one no variant uses is E0392. With
every entry pinned there would be no AnyStore<T> at all, both entries would land in the same
AnyStore, and enumerate would refuse it.
§All of it at once
A binder, the type, a name and the instantiation it implements, in that order:
struct Foo<'a, T>(&'a T);
#[sealed(
for<'a, T> Foo<'a, T> as Bar: Dummy<i32>
)]
trait Dummy<X> {}
impl<'a, T> Dummy<i32> for Foo<'a, T> {}§The list is checked in both directions
Every entry is checked, which is why the trait’s type and const parameters have to be supplied
for it: either the type names them itself, as Boxed<T> does under trait Store<T>, or the
entry annotates its instantiation, as in Plain: Store<i32>. An entry that does neither is
refused, since nothing could then tell whether it implements the trait at all.
A lifetime is never asked for, and not merely because inference usually copes. A type cannot
implement the same trait at two different lifetimes: two such impls overlap, and coherence
rejects them, so there is never more than one candidate to disambiguate. #[sealed(Plain)]
under trait Foo<'a> is therefore accepted and checked: an entry implementing no Foo at all
is still caught.
§The seal is as precise as the list
The marker carries the same type and const parameters the trait does, so Plain: Store<i32>
permits Plain to implement Store<i32> and nothing else: an unlisted impl Store<f64> for Plain is rejected:
struct Plain;
#[sealed(Plain: Store<i32>)]
trait Store<T> {}
impl Store<i32> for Plain {}
impl Store<f64> for Plain {} // error: not permitted to implement `Store` hereAn entry naming the parameters instead, like Boxed<T>, permits every instantiation, which is
what naming them says. Lifetimes are not on the marker, for the reason above: they could never
tell two entries apart.
§What the seal is worth
The marker trait is private to the module the attribute is written in, and carries a supertrait private one level deeper. Naming the marker is therefore not enough to satisfy it: the only place both can be implemented is inside the generated module, which nothing but this macro writes. Code sitting directly beside the sealed trait cannot opt a type in, which a single level of privacy would have allowed.
The cost is that permitted types must be nameable from that module, so they have to live at module level. A type declared inside a function body cannot be sealed, because no module nested in a function can refer to it.