Why is Copy a subtrait of Clone? Understanding the design rationale behind semantic inconsistency
⚓ Rust 📅 2025-08-18 👤 surdeus 👁️ 10Hi everyone,
I've been thinking about the Copy/Clone trait relationship and struggling to understand the design rationale. I've searched extensively but couldn't find discussions about this specific design decision, so I'm hoping someone here can help clarify or point me to relevant resources.
Current Situation
Currently, Copy is a subtrait of Clone, which allows this potentially confusing behavior:
#[derive(Copy, Debug)]
struct Example(u32);
impl Clone for Example {
fn clone(&self) -> Example {
Example(1) // Different behavior than assignment!
}
}
fn main() {
let a = Example(2);
let b = a; // Copy semantics: Example(2)
let c = a.clone(); // Custom Clone: Example(1)
println!("{:?} {:?} {:?}", a, b, c); // Example(2) Example(2) Example(1)
}
The Semantic Issue
According to RFC 1521, Copy types should guarantee that Clone::clone == ptr::read for T: Copy. However, the current design allows custom Clone implementations that can violate this invariant, leading to potentially confusing behavior where assignment and .clone() do different things.
Alternative Design I'm Considering
I keep wondering why Rust didn't adopt this alternative approach:
- Make Copy and Clone orthogonal (no inheritance relationship)
- Provide a implementation:
impl<T: Copy> Clone for Tin libcore for all types that implements Copy to have aptr::readbasedclone()implemention. - This would guarantee semantic consistency: Copy types always clone via
ptr::read - Generic code could use
T: Copyfor "cheap implicit copy that alwaysptr::read" vsT: Clonefor "potentially expensive explicit copy"
Benefits of Alternative Approach
- Semantic consistency: Eliminates the assignment vs
.clone()behavior mismatch - Type system simplification: Removes the subtrait relationship
- Better expressiveness: Generic bounds more clearly express intent
- Improved UX: No need for
#[derive(Copy, Clone)]- just#[derive(Copy)]
Migration Feasibility
This seems technically feasible:
- Edition mechanism could handle the transition
- Automatic fix tools could remove conflicting Clone implementations
- The blanket impl pattern is common in std library
My Questions
-
What am I missing? The alternative design seems to offer better semantics, simpler implementation, and improved developer experience. Are there technical constraints or design considerations I'm overlooking?
-
Where can I find discussions about such design decisions? I'd love to read the original discussions that led to the current Copy/Clone relationship, but I'm not sure where to look for historical design rationale.
I'm genuinely curious to understand the reasoning behind this design choice. Any insights or pointers to relevant discussions would be greatly appreciated!
Thanks for your patience with my questions!
7 posts - 3 participants
🏷️ Rust_feed