#[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
An entry is a type, written plainly or as a path, as in #[sealed(Square, shapes::Circle)], and
that is all of it where neither the trait nor the type is generic. Cases where one or both of
them are generic are presented in the next sections.
§A generic trait
A generic trait has to be told which of its instantiations the entry implements.
Entry: Trait<..> says which:
struct Plain;
#[sealed(Plain: Store<i32>)]
trait Store<T> {}
impl Store<i32> for Plain {}One instantiation can be enough, but the type may implement the trait at every one of them.
for<..> declares a parameter for the entry to instantiate with:
struct Plain;
// every `Store<T>`, not just one
#[sealed(for<T> Plain: Store<T>)]
trait Store<T> {}
impl<T> Store<T> for Plain {}A parameter the binder declares can carry bounds:
struct Plain;
#[sealed(for<T: Debug> Plain: Store<T>)]
trait Store<T> {}
impl<T: Debug> Store<T> for Plain {}The bounds are part of what is sealed: Plain is permitted Store<T> only where T: Debug, so
an impl<T> Store<T> for Plain covering every T is refused.
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.
§A generic type
The parameters a for<..> declares serve the type just as well, which is how a trait with no
parameters of its own seals a generic type:
struct Ref<'a, T>(&'a T);
#[sealed(
for<'a, T: Shape> Ref<'a, T>,
Square,
Circle,
)]
trait Shape {}
impl<'a, T: Shape> Shape for Ref<'a, T> {}§A generic type under a generic trait
One binder covers both, and a name it declares may stand in the type and the instantiation alike.
struct Boxed<U>(U);
// every `Boxed<U>`, each at the matching `Store<U>`
#[sealed(for<U> Boxed<U>: Store<U>)]
trait Store<T> {}
impl<U> Store<U> for Boxed<U> {}The binder’s names are its own, so where they go is what counts, not what they are called:
for<U, V> Pair<U, V>: Store<V, U> seals Pair<U, V> at Store<V, U>, swapped. And the
instantiation’s arguments are ordinary types, so a parameter can sit inside a larger one rather
than be the argument itself: for<T> Keyed<T>: Store<Vec<T>>.
§as Name
Names the entry. Only enumerate reads it: a variant is otherwise named
after the type it holds, and two entries whose names come out the same would be one variant
twice, which is refused. as Name gives one of them a name of its own. This happens 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]
// without `as`, both would take the last segment `Foo`
#[sealed(a::Foo as Left, b::Foo as Right)]
trait Shape {}
impl Shape for a::Foo {}
impl Shape for b::Foo {}
let _ = AnyShape::Left(a::Foo); // see enumerateThe 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>, for<T> Boxed<T>: Store<T>)]
trait Store<T> {}
impl Store<i32> for Plain {}
impl Store<f64> for Plain {}
impl<T> Store<T> for Boxed<T> {}
// 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, since an enum may not declare one no variant uses. Drop
Boxed<T> and nothing is left to be generic over: both entries become variants of one plain
AnyStore, so Plain converts into it two ways and into_enum has two answers. enumerate
refuses that. Keeping the enum generic is what puts the two entries in AnyStore<i32> and
AnyStore<f64>, one Plain apiece.
§All of it at once
A binder, the type, a name and the instantiation it implements, in that order:
struct Ref<'a, T>(&'a T);
#[sealed(
for<'a, T> Ref<'a, T> as RefStore: Store<i32>
)]
trait Store<T> {}
impl<'a, T> Store<i32> for Ref<'a, T> {}§The list is checked in both directions
Every entry is checked, which is why a trait that declares type or const parameters needs them
supplied for each of its entries, and the instantiation is what supplies them:
Plain: Store<i32> pins them, for<T> Boxed<T>: Store<T> passes on what its binder declared.
An entry without one is refused, since nothing could then tell whether it implements the trait
at all. A trait declaring none asks nothing, which is why #[sealed(Square, Circle)] above
needs no annotation.
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 whose binder supplies them instead, like for<T> Boxed<T>: Store<T>, permits every
instantiation, which is what the binder 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.