Is there a way to make all structs derive clone/copy/debug/default by default?

Way too often, I encounter libraries that don't include clone/copy/debug/default (let alone those that don't include a new() method for structs!). The lack of standard features through pure calelessness is outright painful.

Is there a way instead of defaulting structs to no clone/copy/debug/default unless stated otherwise, default to ALL structs deriving clone/copy/debug/default unless stated otherwise (especially those in crates I've added to my project.... can't count the number of times I've needed a default of a struct for a unit test but there is none, and frequently no new() function either! Forcing me to generate them through long methods instead of just calling a default or new on the struct and throwing in a few edge cases.) It's inefficient and counter-intuitive the way it currently is.

I don't think there's a way when depending on those types from a downstream crate. If upstreaming doesn't work (though it tends to), I would create my own wrapper types and implement them.

I do wish Rust (or any language) has support for "value types" that have those derived by default, and maybe PartialEq and Eq as well. Java has record types which have specific guarantees over and above normal "struct"s.

There's the odd case where you hold a password and don't want it to leak through Debug.

2 Likes

There isn't anything like it, and it's very unlikely that Rust will get it.

The closest you can get is some boilerplate reduction for your own structs, like


Lack of copying and inability to create instances of structs can be important for correctness in Rust. Overriding this in other crates would be unsafe, e.g. String is a struct, but copying it would lead to a double free. The default Clone implementation is naive, and isn't guaranteed to correctly clone more complex types.

Sometimes instances of types have special logic, even if the types themselves are trivial — drop guards for locks and semaphores, tokens for resource access, monomorphisation of run-time checks like availability of SIMD instructions. They could be technically easy to instantiate or clone, but doing so could break whatever logic they control.

Rust prefers to be explicit and careful about safety. If someone hasn't thought about copyability of their struct, it's safer to disallow copying (it's just inconvenient or less useful) than to allow copying (it can cause crashes or bypass APIs).

2 Likes

Default can also also be unsound due to violating invariants, both in the field values and for crates that gate all creation of their types (for things like singletons).

Debug... probably less likely to be unsound, but could cause infinite loops or just massive output.


And even if those weren't concerns, it still wouldn't always be possible with the current coherence system.

// These two implementations will conflict if `MyStruct: Debug`
impl<T: Debug> MyTrait for Vec<T> {}
impl MyTrait for Vec<MyStruct> {}

And when it is possible in one crate, the possibility of conflicting implementations would effectively obliterate a lot of upstream's ability to make non-breaking changes, and also destroy dependency compatibility. Unless the language grew something like a whole new variety of coherence and/or specialization for this purpose, anyway.

1 Like

My first upstream contribution was adding Default, Debug, Copy, Clone, Partial Ord, Ord, PartialEq, Eq, and Hash to the type definitions in a GIS library. I felt OPs pain when I remembered looking at the source code and realizing there was no reason the type of interest couldn't be Debug, PartialOrd, maybe even Copy, depending on what the type was. And my feeling is that this would make their code more usable for everybody with my use case, so I wrote a PR, and it was accepted over the course of a few weeks.

As others have mentioned, no one set of traits will make a suitable default for all types, and my own experience is that some kinds of data structures get the same derives over and over, with a common set for structs holding my data, another for enums vs types that wrap the inconvenient types without Debug impls and such, and where implementing them might not even make sense.

In this case, I embrace the effective use of a little boilerplate. WWII tanks had plates made out of steel. Tanks in Rust are made out of boilerplate.

2 Likes