Yeah, for my custom derives I've been using bounds on field types rather than on generic parameters for a while, now; I've always found the std library derives to be quite limited in that fashion. That a newtype around *const String
does #[derive]
Copy, Clone
just fine, but that a newtype<S>
around *const S
is not Copy
when S = String
has always seemed to me to be an inconsistency of the generics mental model.
If you want to use a stdlib derive and yet have these improved semantics, you need to jump through some hoops:
#[derive(Clone, Copy)]
pub struct MyPtr_<Ptr>(Ptr); // `Copy where Ptr : Copy`
/// This way we do get `Copy where *const S : Copy`, and thus _always_ `Copy`!
pub type MyPtr<S> = MyPtr_<*const S>;
So, the "smartest"/ least restrictive solution for the #[derive(Default)]
is to bound on the field types present on that very variant: it's the exact set of necessary bounds.
- One drawback to this approach, but I find the argument to be quite moot, is that it may be slightly easier to make breaking changes if the bounds are "too optimal" all the time
But, since all the other derive usages are suboptimal already, alas, I'd say that we have to bound the generic type parameters instead, for the sake of consistency. Just know that we may end up with instances of:
// #[derive(Default)]
// pub enum Enum<T, …> {
// // #[default]
// A(Option<T>),
// …
// }
#[derive(Default)]
pub enum Enum_<A, …> {
#[default]
A(A),
…
}
pub type Enum<T, …> = Enum_<Option<T>, …>;
as we already have to do for other #[derive]
s (mainly, PartialEq
and Eq
when wanting to have your type feature StructuralEq
on stable Rust).
That being said, bounding only on the generic type parameters appearing on that variant could also lead to breaking changes: a change of implementation whereby the #[default]
variant changes could thus cause bound changes for it being Default
!
So, until we get better tools to get more fine-grained control over these things1, we'd have to settle for the least dangerous / future-breaking approach: bounds on each and every generic parameter appearing on the enum
1Note that since this suggested syntax is the first time we get an extra parameter, which naturally leads to future extensions where parameters could be fed to it, we could perfectly envision offering a way to loosen these bounds in the future:
#[derive(Default)]
pub enum Enum<T, …> {
#[default(where Option<T> : Default)]
A(Option<T>),
…
}