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.