I think we could do something like dyn SealedTrait: Sized as an optimization.
We could enable it with something like
#[derive(SizedTraitObject)] // bikeshed
seal trait SealedTrait {}
This would enable some checks
- All implementers are
Sized
- All generic parameters that are used on a struct in the impl are used in the trait.
i.e
struct Err<E: Error>(E);
struct DatabaseError;
// Valid
#[derive(SizedTraitObject)]
seal trait CustomError_EX1 {}
impl CustomError_EX1 for Err<std::fmt::Error> {} // all generic parameters are filled, so this is fine
impl CustomError_EX1 for Striing {} // no generic parameters, so this is also fine
#[derive(SizedTraitObject)]
seal trait CustomError_EX2<E> {}
impl<E: Error> CustomError_EX2<E> for Err<E> {} // fine, all generic parameters used on struct are used on CustomError_EX2
impl<E> CustomError_EX2<E> for String {} // fine, String has no generic parameters
// Invalid
impl<E: Error> CustomError_EX1 for Err<E> {} // the type parameter E is not constrained by CustomError_EX1, so this is invalid
impl<T, E: Error> CustomError_EX2<T> for Err<E> {} // the type parameter E is not constrained by CustomError_EX1, so this is invalid
This is similar to how enums currently work.
But at that point you could just use enums, where each variant holds a single type, and has a From impl for each variant for ergonomics.