use std::sync::Arc;
struct NoClone;
struct WrapArc<T>(Arc<T>);
fn main() {
let foo = WrapArc(Arc::new(NoClone));
let foo_ = foo.clone();
}
Do you think this code should compile?
What about the following code:
struct AlwaysEq<T>(T);
impl<T> PartialEq for AlwaysEq<T> {
fn eq(&self, _other: &Self) -> bool {
true
}
}
impl<T> Eq for AlwaysEq<T> {}
struct NotEq;
struct WrapAlwaysEq<T>(AlwaysEq<T>);
fn assert_is_eq(_: impl Eq) {}
fn main() {
let x = WrapAlwaysEq(AlwaysEq(NotEq));
assert_is_eq(x);
}
The second example is a bit far fetched, but you probably answered yes.
Why not?
The
implementation of #[derive(Clone)] in the Rust compiler
generates a Clone
implementation with the following requirements on the
derived type:
- All fields must be
Clone
. - All generic parameters must be
Clone
.
Can you spot the issue here? It's the latter requirement: we cannot just
require all generic parameters to be Clone
, as we cannot assume they are used
in such a way that requires them to be cloned.^1
This applies to practically all builtin derive traits, such as Clone
,
PartialEq
, Eq
, or
even Debug
.
What can we do to fix this?
There are two solutions to this. Both require deleting that second requirement.
The hard way
We could create a Rust RFC, hopefully not bikeshed it to death, and get it stabilized in the next Rust edition as it is a breaking change.^2
This would take 4+ years to stabilize and be available to everyone. That sucks, but is the correct thing to do in the long-term.
The quick way
We can just write our own macro that generates the following code:
/* input */
struct WrapArc<T>(Arc<T>);
/* generated code */
impl<T> Clone for WrapArc<T>
where
Arc<T>: Clone,
// and so on, `FieldType: DerivedTrait` for each field
{
// ...
}
This does the job correctly.
And it's not even hard to do. I know people who do this internally in their company codebases - it's not much code.
So I've opened an issue
about replicating the builtin derive traits in a less restrictive and thus
correct way in the derive_more
crate's GitHub repository. The reason I chose
this crate is because it already has a lot of users and is the main place for
derive implementations.
Replicating already-existing behaviour of the std may not be in the scope of the crate, which is a perfectly fine stance to take. If that doesn't get accepted, I'll probably create my own crate and release it on crates.io.
Stay tuned, I'll update this blog post.