From a purely technical standpoint (and not whether we should support this), some form of annotation could work:
#[derive(PartialEq)]
#[derive_final] // Unbounded bikeshed on naming...
struct Foo {
// Fields...
}
or just:
#[derive_final(PartialEq)]
struct Foo {
// Fields...
}
This has a few problems tho.
- There is no way for custom derive macros to know whether
#[derive_final] was done on the type of an field inside the currently-being-derived type.
- This can be solved by passing such information to the derive macro, but then deriving becomes other than strictly textual (on the text of the type definition), and you will need parts or the whole text of other referenced types in the fields or variants.
- This solution in turn creates other problems around compiler phases.
- This only gives a measure of protection for
#[derive(..)] but no mechanism for protecting bad equivalent impls.
Another way to solve 1. is by adding eq_final:
pub trait PartialEq<Rhs: ?Sized = Self> {
fn eq(&self, other: &Rhs) -> bool { self.eq_final(other) }
fn eq_final(&self, other: &Rhs) -> bool;
}
And then we #[derive_final] with:
#[derive_final(PartialEq)]
struct Foo(u8);
// Generates:
impl PartialEq for Foo {
fn eq(&self, other: &Rhs) -> bool {
self.0.eq_final(&other.0)
}
fn eq_final(&self, other: &Rhs) -> bool {
panic!("PartialEq for Foo is #[derive_final]")
}
}
And when we add a normal derive on top of that, we get:
#[derive(PartialEq)]
struct Bar(Foo);
// Generates:
impl PartialEq for Foo {
fn eq_final(&self, other: &Rhs) -> bool { self.0.eq_final(&other.0) }
}
Now, when you try to execute Bar::eq_final or Bar::eq, you will get a panic!.
However, this mechanism is quite invasive and also uses dynamic correctness checks, which is ungreat.