Skip to content(if available)orjump to list(if available)

# [derive(Clone)] Is Broken

# [derive(Clone)] Is Broken

78 comments

·July 4, 2025

lidavidm

Someone on the issue they made explained why Clone is "broken": https://github.com/JelteF/derive_more/issues/490#issuecommen...

Which links to this blog post explaining the choice in more detail: https://smallcultfollowing.com/babysteps/blog/2022/04/12/imp...

samsartor

I have a crate with a "perfect" derive macro that generates where clauses from the fields instead of putting them on the generic parameters. It is nice when it works, but yah cyclical trait matching is still a real problem. I wound up needing an attribute to manually override the bounds whenever they blow up: https://docs.rs/inpt/latest/inpt/#bounds

sbt567

from Niko's post:

> In the past, we were blocked for technical reasons from expanding implied bounds and supporting perfect derive, but I believe we have resolved those issues. So now we have to think a bit about semver and decide how much explicit we want to be.

null

[deleted]

loa_in_

Automatically deriving Clone is a convenience. You can and should write your own implementation for Clone whenever you need it and automatically derived implementation is insufficient.

josephg

But this issue makes it confusing & surprising when an automatically derived clone is sufficient and when its not. Its a silly extra rule that you have to memorise.

By the way, this issue also affects all of the other derivable traits in std - including PartialEq, Debug and others. Manually deriving all this stuff - especially Debug - is needless pain. Especially as your structs change and you need to (or forget to) maintain all this stuff.

Elegant software is measured in the number of lines of code you didn't need to write.

jhugo

> Elegant software is measured in the number of lines of code you didn't need to write.

Strong disagree. Elegant software is easy to understand when read, without extraneous design elements, but can easily have greater or fewer lines of code than an inelegant solution.

0rzech

It's surprising up to the moment the compilation error tells you that all of the members have to implement derived trait.

Nevertheless, it would be cool to be able to add #[noderive(Trait)] or something to a field not to be included in automatic trait implementation. Especially that sometimes foreign types do not implement some traits and one has to implement lots of boilerplate just to ignore fields of those types.

I know of Derivative crate [1], but it's yet another dependency in an increasingly NPM-like dependency tree of a modern Rust project.

All in all, I resort to manual trait implementations when needed, just as GP.

[1] https://crates.io/crates/derivative

0rzech

Apparently, Derivative is unmaintained [1], but there is Derive_more [2], Educe [3] and Derive-where [4], if anyone is interested.

[1] https://rustsec.org/advisories/RUSTSEC-2024-0388.html

[2] https://crates.io/crates/derive_more

[3] https://crates.io/crates/educe

[4] https://crates.io/crates/derive-where

atoav

The same is true for memory allocation. But I do not think it makes sense that everybody has to write memory allocators from scratch because a few special cases require it.

j-pb

I disagree; Elegant software is explicit. Tbh I wouldn't mind if we got rid of derives tomorrow. Given the ability of LLMs to generate and maintain all that boilerplate for you, I don't see a reason for having "barely enough smarts" heuristic solutions to this.

I rather have a simple and explicit language with a bit more typing, than a perl that tries to include 10.000 convenience hacks.

(Something like Uiua is ok too, but their tacitness comes from simplicity not convenience.)

Debug is a great example for this. Is derived debug convenient? Sure. Does it produce good error messages? No. How could it? Only you know what fields are important and how they should be presented. (maybe convert the binary fields to hex, or display the bitset as a bit matrix)

We're leaving so much elegance and beauty in software engineering on the table, just because we're lazy.

zwnow

I am sorry but Uiua and LLM generated code? This has to be a shitpost

jhugo

> 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.

No, this is backwards. We have to require all generic parameters are Clone, as we cannot assume that any are not used in a way that requires them to be Clone.

> The reason this is the way it is is probably because Rust's type system wasn't powerful enough for this to be implemented back in the pre-1.0 days. Or it was just a simple oversight that got stabilized.

The type system can't know whether you call `T::clone()` in a method somewhere.

chrismorgan

> The type system can't know whether you call `T::clone()` in a method somewhere.

It’s not about that, it’s about type system power as the article said. In former days there was no way to express the constraint; but these days you can <https://play.rust-lang.org/?gist=d1947d81a126df84f3c91fb29b5...>:

  impl<T> Clone for WrapArc<T>
  where
      Arc<T>: Clone,
  {
      …
  }

enricozb

For structs, why couldn't rust check the necessary bounds on `T` for each field to be cloned? E.g. in

    #[derive(Clone)]
    struct Weird<T> {
      ptr: Arc<T>,
      tup: (T, usize)
    }

for `ptr`, `Arc<T>: Clone` exists with no bound on `T`. But for `tup`, `(T, usize): Clone` requires `T: Clone`.

Same thing for other derives, such as `Default`.

jhugo

Because it doesn't know if you're relying on T being Clone in method bodies. The internal behavior of methods is not encoded in the type system.

RGBCube

What?

The way the derives work is they generate code by utilizing the fields and their types. Here is a trivial implementation (of a custom trait, rather than Clone. still holds though) which will prove you wrong:

<https://github.com/cull-os/carcass/blob/master/dup%2Fmacros%...>

berkes

> The type system can't know whether you call `T::clone()` in a method somewhere.

Why not?

jhugo

Types don't carry behavioral information about what the method does internally. Everything about a method is known from its signature.

The compiler doesn't introspect the code inside the method and add additional hidden information to its signature (and it would be difficult to reason about a compiler that did).

ninkendo

> Types don't carry behavioral information about what the method does internally.

I don’t remember specifics, but I very distinctly remember changing a method in some way and Rust determining that my type is now not Send, and getting errors very far away in the codebase because of it.[0]

If I have time in a bit I’ll try and reproduce it, but I think Send conformance may be an exception to your statement, particularly around async code. (It also may have been a compiler bug.)

[0] It had something to do with carrying something across an await boundary, and if I got rid of the async call it went back to being Send again. I didn’t change the signature, it was an async method in both cases.

delta_p_delta_x

> Types don't carry behavioral information about what the method does internally.

I was under the impression type inference meant that the implementation of a function directly determines the return type of a function, and therefore its signature and type.

null

[deleted]

xupybd

Amazing site from someone so young

moomin

Haskell does this. If you derive Eq, it puts a condition on the generic parameter(s) requiring them to be Eq as well. Then if you use it with something that doesn’t implement Eq, your generic type doesn’t either.

It helps if you can express these preconditions in the first place, though.

hardwaresofton

Haskell has, like-for-like, a better type system than Rust.

That said, Rust is just enough Haskell to be all the Haskell any systems programmer ever needed, always in strict mode, with great platform support, a stellar toolkit, great governance, a thriving ecosystem and hype.

Lots of overlap in communities (more Haskell -> Rust than the other way IMO) and it's not a surprise :)

01HNNWZ0MV43FF

I have respect for Haskell because I saw it long before I saw Rust, and I love the ideas, but I never got around to actually using Haskell.

I think an IO monad and linear types [1] would do a lot for me in a Rust-like

[1] Affine types? Linear types? The ones where the compiler does not insert a destructor but requires you to consume every non-POD value. As someone with no understanding of language theory, I think this would simplify error handling and the async Drop problem

yccs27

I can understand why Rust didn't implement an IO monad (even though I'd love it), but linear types seem like they would fit right in with the rest of Rust. Not sure why they didn't include them.

There's actually two different ways that Rust types can always be discarded: mem::forget and mem::drop. Making mem::forget unsafe for some types would be very useful, but difficult to implement without breaking existing code [1].

Btw, you are correct with linear types - Affine types allow discarding (like Rust already does).

[1] https://without.boats/blog/changing-the-rules-of-rust/

hardwaresofton

I used Haskell for a while and eventually switched over (and intentionally target Rust for many new projects, though I suspect I should be doing more Go for really simple things that just don't need more thought).

One large problem with Haskell that pushes people to Rust is strictness, I think -- laziness is a feature of Haskell and is basically the opposite direction from where Rust shines (and what one would want out of a systems language). It's an amazing feature, but it makes writing performant code more difficult than it has to be. There are ways around it, but they're somewhat painful.

Oh there's also the interesting problem of bottom types and unsafety in the std library. This is a HUGE cause of consternation in Haskell, and the ecosystem suffers from it (some people want to burn it all down and do it "right", some want stability) -- Rust basically benefited from starting later, and just making tons of correct decisions the first time (and doing all the big changes before 1.0).

That said, Haskell's runtime system is great, and it's threading + concurrency models are excellent. They're just not as efficient as Rust's (obviously) -- the idea of zero cost abstractions is another really amazing feature.

> [1] Affine types? Linear types? The ones where the compiler does not insert a destructor but requires you to consume every non-POD value. As someone with no understanding of language theory, I think this would simplify error handling and the async Drop problem

Yeah, the problem is that Affine types and Linear types are actually not the same thing. Wiki is pretty good here (I assume you meant to link to this):

https://en.wikipedia.org/wiki/Substructural_type_system

Affine is a weakening of Linear types, but the bigger problem here is that Haskell has a runtime system -- it just lives in a different solution-world from Rust.

For rust, Affine types are just a part of the way they handle aliasing and enable a GC-free language. Haskell has the feature almost... because it's cool/powerful. Yes, it's certainly useful in Haskell, but Haskell just doesn't seem as focused on such a specific goal, which makes sense because it's a very research driven language. It has the best types (and is the most widely adopted ML lang IIRC) because it focuses on being correct and powerful, but the ties to the ties to practicality are not necessarily the first or second thought.

It's really something that Rust was able to add something that was novel and useful that Haskell didn't have -- obvious huge credit to the people involved in Rust over the years and the voices in the ecosystem.

hyperbrainer

> 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.

I don't understand what "used in such a way requires them to be cloned" means. Why would you require that?

xvedejas

Right now the derive macro requires `T` be `Clone`, but what we actually want to require is only that each field is clone, including those that are generic over `T`. eg `Arc<T>` is `Clone` even though `T` isn't, so the correct restriction would be to require `Arc<T>: Clone` instead of the status quo which requires `T: Clone`

tinco

That's the crux of the article. There's no good reason for this requirement, at least none that arise in the article, so the author concludes it must be a mistake.

I think it's a bit cynical that it would cost at least 4 years for this change to be admitted into the compiler. If the author is right and there is really no good reason for this rule, and I agree with the author in seeing no good reason, then it seems like something that could be changed quite quickly. The change would allow more code to compile, so nothing would break.

The only reason I could come up with for this rule is that for some other reason allowing non complying type parameters somehow makes the code generation really complex and they therefore postponed the feature.

the_mitsuhiko

> The only reason I could come up with for this rule is that for some other reason allowing non complying type parameters somehow makes the code generation really complex and they therefore postponed the feature.

The history of this decision can be found in details in this blog post: https://smallcultfollowing.com/babysteps//blog/2022/04/12/im...

The key part:

> This idea [of relaxing the bounds] is quite old, but there were a few problems that have blocked us from doing it. First, it requires changing all trait matching to permit cycles (currently, cycles are only permitted for auto traits like Send). This is because checking whether List<T> is Send would not require checking whether Option<Rc<List<T>>> is Send. If you work that through, you’ll find that a cycle arises. I’m not going to talk much about this in this post, but it is not a trivial thing to do: if we are not careful, it would make Rust quite unsound indeed. For now, though, let’s just assume we can do it soundly.

> The other problem is that it introduces a new semver hazard: just as Rust currently commits you to being Send so long as you don’t have any non-Send types, derive would now commit List<T> to being cloneable even when T: Clone does not hold.

> For example, perhaps we decide that storing a Rc<T> for each list wasn’t really necessary. Therefore, we might refactor List<T> to store T directly […] We might expect that, since we are only changing the type of a private field, this change could not cause any clients of the library to stop compiling. With perfect derive, we would be wrong.2 This change means that we now own a T directly, and so List<T>: Clone is only true if T: Clone.

josephg

Yeah; I think this argument makes sense. With perfect derive, #[derive(Clone)] has a bunch of implicit trait bounds which will change automatically as the struct changes. This has semver implications - and so we might want to be explicit about this rather than implicit.

We could solve this by having developers add trait bounds explicitly into the derive macro.

Currently this:

    #[derive(Clone)]
    struct Foo<T>(Arc<T>)
expands to:

    impl Clone for Foo where T: Clone { ... }
Perfect derive would look at the struct fields to figure out what the trait bounds should be. But it might make more sense to let users set the bound explicitly. Apparently the bon crate does it something like this:

    #[derive(Clone(bounds(Arc<T>: Clone)))]
Then if you add or remove fields from your struct, the trait bounds don't necessarily get modified as a result. (Or, changing those trait bounds is an explicit choice by the library author.)

rocqua

A type might have a generic parameter T, but e.g. use it as a phantom marker.

Then even if T isn't cloneable, the type might still admit a perfectly fine implementation of clone.

qwertox

LLMs are broken, too:

> "Of course. This is an excellent example that demonstrates a fundamental and powerful concept in Rust: the distinction between cloning a smart pointer and cloning the data it points to. [...]"

Then I post the compiler's output:

> "Ah, an excellent follow-up! You are absolutely right to post the compiler error. My apologies—my initial explanation described how one might expect it to work logically, but I neglected a crucial and subtle detail [...]"

Aren't you also getting very tired of this behavior?

the_mitsuhiko

> Aren't you also getting very tired of this behavior?

The part that annoys me definitely is how confident they all sound. However the way I'm using them is with tool usage loops and so it usually runs into part 2 immediately and course corrects.

bt1a

Well, they're usually told that they're some unicorn master of * languages, frameworks, skillsets, etc., so can you really fault them? :)

rfoo

TBH I'm tired of only the "Ah, an excellent follow-up! You are absolutely right <...> My apologies" part.

IshKebab

Yeah they definitely didn't do that in the past. We've lost "as a large language model" and "it's important to remember" but gained "you're absolutely right!"

I would have thought they'd add "don't apologise!!!!" or something like that to the system prompt like they do to avoid excessive lists.

codedokode

Languages like Rust and C seem to be too complicated for them. I also asked different LLMs to write a C macro or function that creates a struct and a function to print it (so that I don't have to duplicate a field list) and it generates plausible garbage.

ramon156

You should check Twitter nowadays, people love this kind of response. Some even use it as an argument

darkwater

And this is why basically LLMs are "bad". They have already reached critical mass adoption, they are right or mostly right most of the time but they also screw up badly many times as well. And people will just not know, trust blindly and going even deeper down in the spiral of the total absence of critical judgement. And yeah, it also happened with Google and search engines back in the day ("I found it on the web so it must be true") but now with LLMs it is literally tailored to what you are asking, for every possible question you can ask (well, minus the censored ones).

I keep thinking the LLM contribution to humanity is/will be a net negative in the long run.

chrismorgan

> they are right or mostly right most of the time

It’s times like this when I wonder if we’re even using the same tools. Maybe it’s because I only even try to actively use them when I expect failure and am curious how it will be (occasionally it just decides to interpose itself on a normal search result, and I’m including those cases in my results) but my success rate with DuckDuckGo Assist (GPT-4o) is… maybe 10% of the time success but the first few search results gave the answer anyway, 30% obviously stupidly wrong answer (and some of the time the first couple of results actually had the answer, but it messed it up), 60% plausible but wrong answer. I have literally never had something I would consider an insightful answer to the sorts of things I might search the web for. Not once. I just find it ludicrously bad, for something so popular. Yet somehow lots of people sing their praises and clearly have a better result than me, and that sometimes baffles, sometimes alarms me. Baffles—they must be using it completely differently from me. Alarms—or are they just failing to notice errors?

(I also sometimes enjoy running things like llama3.2 locally, but that’s just playing with it, and it’s small enough that I don’t expect it to be any good at these sorts of things. For some sorts of tasks like exploring word connections when I just can’t quite remember a word, or some more mechanical things, they can be handy. But for search-style questions, using models like GPT-4o, how do I get such consistently useless or pernicious results from them!?)

bt1a

Yet theyre fantastic personal tutors / assistants who can provide a deeply needed 1:1 learning interface for less privileged individuals. I emphasize 'can'. Not saying kids should have them by their side in their current rough around the edges and mediocre intelligent forms. Many will get burned as you describe, but it should be a lesson to curate information from multiple sources and practice applying reasoning skills!

renewiltord

Haha, I encountered the opposite of this when I did a destructive thing recently but first asked Gemini, then countered it saying it’s wrong and it insisted it was right. So the reality they encountered is probably that: it either is stubbornly wrong or overly obsequious with no ability to switch.

My friend was a big fan of Gemini 2.5 Pro and I kept telling him it was garbage except for OCR and he nearly followed what it recommended. Haha, he’s never touching it again. Every other LLM changed its tune on pushback.

null

[deleted]

kzrdude

The only thing that needs change with derive(Clone) is to add an option to it so that you easily can customize the bounds. Explicitly.

null

[deleted]

m3talsmith

Just looking at the examples, you can tell that they wouldn't compile: the other structs passed in don't derive the trait as well, nor implement it. It's really simple, not broken.

csomar

Derive Clone is not broken. It is basic. I’d say this is a good area for a dependency but not the core Rust functionality. Keep derive simple and stupid, so people can learn they can derive stuff themselves. It also avoids any surprises.

josephg

I disagree. I think the current behaviour is surprising. The first time I ran into this problem, I spent half an hour trying to figure out what was wrong with my code - before eventually realising its a known problem in the language. What a waste of time.

The language & compiler should be unsurprising. If you have language feature A, and language feature B, if you combine them you should get A+B in the most obvious way. There shouldn't be weird extra constraints & gotchas that you trip over.

MangoToupe

I don't see it as a problem, personally. It's consistent behavior that I don't find surprising at all, perhaps because I internalized it so long ago. I can understand your frustration tho

> in the most obvious way.

What people find obvious is often hard to predict.

Ar-Curunir

The solution is to use a derive macro like derivative or educe.

mrbook22

skill issue. Arc should allow Clone, even if the underlying is not `impl Clone`.

DougBTX

Agreed, a screwdriver isn’t broken just because it isn’t a good hammer. The title seems misleading, I was expecting a bug, memory unsafe etc.

Allowing more safe uses seems OK to me, but obviously expanding the functionality adds complexity, so there’s a trade off.

atemerev

I mean, how this is not core Rust functionality if you need clone for many things unless you want to fight the borrow checker for yet another day?