There may be some implementation difficulty about conflicting impls generated by derive(Copy) and derive(Clone) or manual Clone impls. But since these are builtin, the compiler can handle it by using a #[rustc_interanal_ignore_this_impl_in_presence_of_others]. We can fix the semantics and emit error about conflicting impls over an edition.
This is a breaking change, and needs an edition.
Benefits:
Reduced boilerplate on every ADT definition.
Better produced code, e.g.: impl Clone for T { fn clone(&self) -> Self { *self } } which either saves the optimizer time, or improve the final program performance.
Having different behavior between Clone and Copy, or PartialOrd and Ord is a logic error, and this makes using derive for one and manual implementation for the other a hard error over an edition.
It doesn't need to operate at the trait solver level, It can syntactically disappear the impl block if there is an impl Clone for whatever variant of the type with any generic bounds. If it removes the impl Clone too aggressively, people can just add the Clone derive again.
I'm not saying this is the best way to implement it, just that the implementation side is not dead-end.
If you scan the whole crate, no, but macros cannot do that currently (not even built-in macros), this will be a big change, hurt performance and incrementality, and something that rust-analyzer cannot implement (and as a member of T-rust-analyzer makes me feel deeply uneasy).
I meant a pure syntactic solution, based on syntax heuristics. False positives would result in this feature not working in some rare cases.
Makes sense. So the syntax based solution is probably not a good idea.
What about doing this in the trait solver, but not with completely solving and relying on specialization? Trait solver needs to collect all trait impls from the crate. If it sees a #[ignorable] impl Clone for Foo<Something> and another impl Clone for Foo<Another> or impl Clone for T, it ignores the #[ignorable] one.
The ignorable one becomes completely removed, and the user should add the Clone derive again. It may lead to some surprising corner cases, but I expect it to work in 99% of cases.
To me it sounds like solving the "I don't like writing Clone," with an over-complicated solution which has a high chance of false-positives and/or significant performance regressions.
Also becoming a potential SemVer hazard and taking from Rust's intentional explicitness.
Also, can't this be implemented as an external crate with some #[derive(Clopy)] to explore the actual usefulness for the ecosystem?
Can you please explain this? I don't see any semver risk here.
Since the Clone is super trait of Copy, this is similar to implied bounds. Do you consider T: Copy instead of T: Clone + Copy a bad thing, reducing the Rust's intentional explicitness?
There is a macro_rules_attribute which does this among other things, and also a derive_aliases which is more focused on this problem. We can do an analysis on popularity of this pattern, but it is definitely not a problem invented by me.
If you have a #[derive(Copy)] struct Foo<T> that also implements Clone for Foo<T: Clone> then adding impl Clone for Foo<NonClone> would be a breaking change because it silently disables the existing Clone impl.
This is not an implied bound, it is a logical implication. T: Copy and Copy: Clone, hence T: Clone.
IMO any such experiment will flawed because they do not measure the impact of the breakage you'll get by changing Clone itself.
It wouldn't be silent. You will immediately get a compile error "the trait Clone is not implemented for Foo<T>", because Clone is super trait of Copy. Now you have two choices:
Add a derive(Clone): No breaking change at all.
Do something else, like impl<T: Copy> Clone for Foo<T>, which would be breaking change, but you explicitly opted-in.
So, at least in your current example, I don't think there is any increased semver risk added by this feature.
You are right. implied_bounds are implicit in another dimension, but my point that T: Copy has an implicit Clone bound is unchanged, other than wording.
I think the right feature for abbreviating long derive lines is some kind of derive alias. We could even intentionally overload the Copy derive to mean Copy, Clone similar to what is done for the type Result sometimes.
Right now there's a crate for this, derive_aliases but maybe it should be part of Rust proper.
I agree that this is a useful feature, but I think having Eq derive PartialEq as well and Copy derive Clone as well is good even in presence of that feature.
That is, if we were going to design a Rust-like language today, I think having #[derive(Eq)] is clearly a better choice than #[derive(PartialEq, Eq)]. But the current state of Rust and whether it makes sense to use hacks/editions to prevent breakage is another debate.
I meant changing the Clone/Copy macros. For example macro_rules_attribute uses Copy!, so the original Clone and Copy remain valid and there's no downside of potential breakage as opposed to your proposal.
I disagree: I might have valid reasons to have custom code in eq() but Eq is still the same. In fact I did that in the past and also saw it in other places.
But even if this is true, As you yourself said that making this change now is a whole different matter. And saving typing 7 letters does not justify a serious language and compiler complexity, even not considering all negative effects others have cited, IMO.
You can do this for Eq since it is just a marker trait. Doing this for PartialOrd or Clone would be against the contract of those traits, and a logic error. Even in the Eq case, your intent is better expressed as impl Eq for T {} instead of derive.
It is not just saving 7 letters, it can also improve the codegen for Clone:
impl Clone for T { fn clone(&self) -> Self { *self } }
Which either saves the optimizer time, or improve the final program performance. And if it gets done over an edition, it has additional benefit of preventing the mentioned logic error, and it would no longer need complex compiler features.
Maybe adding a new type DeriveDefault could be better.
If you use derive by accident, the duplicated implementation will directly throw an error, and if you just implement traits by hand, you could easily disable the derive flag.