Why is Copy a subtrait of Clone? Understanding the design rationale behind semantic inconsistency

⚓ Rust    📅 2025-08-18    👤 surdeus    👁️ 3      

surdeus

Hi 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:

  1. Make Copy and Clone orthogonal (no inheritance relationship)
  2. Provide a implementation: impl<T: Copy> Clone for T in libcore for all types that implements Copy to have a ptr::read based clone() implemention.
  3. This would guarantee semantic consistency: Copy types always clone via ptr::read
  4. Generic code could use T: Copy for "cheap implicit copy that always ptr::read" vs T: Clone for "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

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

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

Read full topic

🏷️ Rust_feed