derive_more icon indicating copy to clipboard operation
derive_more copied to clipboard

Duplicates of builtin derives with correct requirements

Open RGBCube opened this issue 6 months ago • 5 comments

Currently, when you do:

#[derive(Clone)]
struct Foo<T>(std::sync::Arc<T>);

struct NoClone;

fn main() {
  let foo = Foo(std::sync::Arc::new(NoClone));
  let foo_ = foo.clone();
}

link to playground

The code fails to compile. This is because the standard Clone and other derive macros that require fields to implement the trait they're deriving generate the following code: (they technically don't generate the code, but the functionality is equivalent)

impl<T> Clone for Foo<T> where
  T: Clone,
  Arc<T>: Clone,
{ ... }

Rather than:

impl<T> Clone for Foo<T> where
  Arc<T>: Clone
  /* and more, FieldT: Clone, for every field */
{ ... }

This is incorrect behaviour and makes the trait impls too narrow to be useful in many cases.

The reason for this behaviour is historical, and I don't believe it's possible to fix without a new edition, but that's far away and such a fix would break a lot of things.

It would be great if a temporary solution could be implemented in this crate to fix this, I can implement it myself if that's needed, but having this in an already widely used crate would be wonderful.

RGBCube avatar Jul 04 '25 19:07 RGBCube

This is an intentional limitation of the standard derive macros intended for conservative protection from accidental breaking changes. Niko Matsakis' blog post calls it "perfect derive" and explains in detail why it's considered harmful.

The usual workaround is writing the trait impl manually, but I think just adding attribute config to allow the user configure where bounds on the impl is the better option. For example bon exposes syntax such as this:

#[derive(Trait(bounds(T: Clone, U: PartialEq)))]

Veetaha avatar Jul 04 '25 23:07 Veetaha

That's really interesting, I hadn't considered that this could lead to semver hazards. Though I suppose it's up to @JelteF if derive_more can host "perfect" Debug/PartialEq/Clone/etc derives then, as it's still really useful to have in internal APIs

RGBCube avatar Jul 05 '25 00:07 RGBCube

I found this crate, which does it perfectly: https://github.com/ModProg/derive-where

RGBCube avatar Jul 05 '25 09:07 RGBCube

Nevermind, that enforces the type to implement the derived traits for all variations of the type arguments, rather than just adding requirements to the impl. So the issue is still up.

RGBCube avatar Jul 05 '25 22:07 RGBCube

@Veetaha

standard derive macros intended for conservative protection from accidental breaking changes

And still, there is the majority of cases where derives are used in user code, and no such concerns are meaningful.

tyranron avatar Jul 07 '25 12:07 tyranron