ExamplesBy LevelBy TopicLearning Paths
408 Intermediate

408: Clone and Copy Traits

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "408: Clone and Copy Traits" functional Rust example. Difficulty level: Intermediate. Key concepts covered: Functional Programming. Rust's ownership model moves values by default — after `let b = a`, `a` is consumed. Key difference from OCaml: 1. **Implicit vs. explicit**: Rust `Copy` types copy implicitly on assignment; OCaml copies are always by reference (shared) unless explicitly duplicated.

Tutorial

The Problem

Rust's ownership model moves values by default — after let b = a, a is consumed. For small stack-allocated types (integers, f32, pairs of floats), this restriction is unnecessary overhead: the value is trivially duplicated by copying bits. For heap-allocated types (strings, vectors), copying must be explicit to avoid unexpected O(n) copies. Rust resolves this with two traits: Copy (implicit bitwise copy, opt-in) and Clone (explicit .clone(), potentially expensive). This distinction makes performance visible in code: an .clone() call signals potential allocation.

Copy is implemented by all primitive types (i32, f64, bool, char), tuples of Copy types, and small structs/enums where all fields are Copy.

🎯 Learning Outcomes

  • • Understand the semantic difference: Copy = implicit bit copy; Clone = explicit potentially-expensive copy
  • • Learn which types can implement Copy (no heap allocation, no Drop, all fields Copy)
  • • See how Vector2D: Copy allows passing by value without move semantics
  • • Understand why LabeledPoint (contains String) can only be Clone, not Copy
  • • Learn how #[derive(Clone, Copy)] works and its requirements
  • Code Example

    // Copy: implicit, bitwise, for small stack types
    #[derive(Copy, Clone)]
    struct Point { x: i32, y: i32 }
    
    let p1 = Point { x: 1, y: 2 };
    let p2 = p1;  // Copy: p1 still valid
    println!("{} {}", p1.x, p2.x);
    
    // Clone: explicit, for heap-allocated types
    let s1 = String::from("hello");
    let s2 = s1.clone();  // Must be explicit
    // s1 and s2 are independent

    Key Differences

  • Implicit vs. explicit: Rust Copy types copy implicitly on assignment; OCaml copies are always by reference (shared) unless explicitly duplicated.
  • Heap safety: Rust's Copy exclusion of heap types prevents accidental O(n) copies; OCaml relies on GC reference counting to manage copies safely.
  • Drop incompatibility: Rust types implementing Drop cannot implement Copy (drop semantics conflict with bitwise copy); OCaml's GC finalizers have no such restriction.
  • Performance visibility: Rust's .clone() calls are explicit in code, signaling potential cost; OCaml's mutations on shared data are equally implicit but have different implications.
  • OCaml Approach

    OCaml values are either unboxed (integers, small variants) or boxed (heap-allocated). All values can be copied in OCaml — the GC manages aliasing. There is no Copy/Clone distinction. String in OCaml is mutable and copied by String.copy. Structural sharing is safe because the GC tracks all references. This simplicity comes at the cost of implicit O(n) copies when you don't intend to share.

    Full Source

    #![allow(clippy::all)]
    //! Clone and Copy Traits
    //!
    //! Copy: implicit bitwise copy for small, stack-only types.
    //! Clone: explicit, potentially expensive duplication.
    
    /// A 2D vector — small, stack-only, implements Copy.
    #[derive(Debug, Clone, Copy, PartialEq)]
    pub struct Vector2D {
        pub x: f32,
        pub y: f32,
    }
    
    impl Vector2D {
        pub fn new(x: f32, y: f32) -> Self {
            Vector2D { x, y }
        }
    
        pub fn zero() -> Self {
            Vector2D { x: 0.0, y: 0.0 }
        }
    
        pub fn magnitude(&self) -> f32 {
            (self.x * self.x + self.y * self.y).sqrt()
        }
    
        pub fn add(self, other: Vector2D) -> Vector2D {
            Vector2D {
                x: self.x + other.x,
                y: self.y + other.y,
            }
        }
    
        pub fn scale(self, factor: f32) -> Vector2D {
            Vector2D {
                x: self.x * factor,
                y: self.y * factor,
            }
        }
    }
    
    /// A point with optional label — Clone but not Copy (contains String).
    #[derive(Debug, Clone, PartialEq)]
    pub struct LabeledPoint {
        pub x: f64,
        pub y: f64,
        pub label: String,
    }
    
    impl LabeledPoint {
        pub fn new(x: f64, y: f64, label: &str) -> Self {
            LabeledPoint {
                x,
                y,
                label: label.to_string(),
            }
        }
    
        pub fn with_label(&self, new_label: &str) -> Self {
            LabeledPoint {
                label: new_label.to_string(),
                ..self.clone()
            }
        }
    }
    
    /// DNA sequence — Clone only (heap allocation).
    #[derive(Debug, Clone, PartialEq)]
    pub struct DNA {
        pub sequence: String,
        pub species: String,
    }
    
    impl DNA {
        pub fn new(sequence: &str, species: &str) -> Self {
            DNA {
                sequence: sequence.to_string(),
                species: species.to_string(),
            }
        }
    
        pub fn mutate(&mut self, position: usize, base: char) {
            if position < self.sequence.len() {
                let mut chars: Vec<char> = self.sequence.chars().collect();
                chars[position] = base;
                self.sequence = chars.into_iter().collect();
            }
        }
    
        pub fn len(&self) -> usize {
            self.sequence.len()
        }
    
        pub fn is_empty(&self) -> bool {
            self.sequence.is_empty()
        }
    }
    
    /// Color type — small enough for Copy.
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    pub struct Color {
        pub r: u8,
        pub g: u8,
        pub b: u8,
        pub a: u8,
    }
    
    impl Color {
        pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
            Color { r, g, b, a: 255 }
        }
    
        pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
            Color { r, g, b, a }
        }
    
        pub fn with_alpha(self, a: u8) -> Self {
            Color { a, ..self }
        }
    
        pub fn blend(self, other: Color, factor: f32) -> Color {
            let f = factor.clamp(0.0, 1.0);
            let inv = 1.0 - f;
            Color {
                r: (self.r as f32 * inv + other.r as f32 * f) as u8,
                g: (self.g as f32 * inv + other.g as f32 * f) as u8,
                b: (self.b as f32 * inv + other.b as f32 * f) as u8,
                a: (self.a as f32 * inv + other.a as f32 * f) as u8,
            }
        }
    }
    
    /// Demonstrates that Copy types can be used after assignment.
    pub fn copy_demonstration() -> (Vector2D, Vector2D) {
        let v1 = Vector2D::new(3.0, 4.0);
        let v2 = v1; // Copy: v1 still valid
        let v3 = v1; // Can use v1 again
        (v2, v3)
    }
    
    /// Demonstrates that Clone requires explicit .clone().
    pub fn clone_demonstration() -> (DNA, DNA) {
        let dna1 = DNA::new("ATCG", "human");
        let mut dna2 = dna1.clone(); // Explicit clone
        dna2.mutate(0, 'G');
        (dna1, dna2) // Both independent
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_vector_copy() {
            let v1 = Vector2D::new(1.0, 2.0);
            let v2 = v1; // Copy
            let v3 = v1; // Still valid
            assert_eq!(v1, v2);
            assert_eq!(v2, v3);
        }
    
        #[test]
        fn test_vector_operations() {
            let v = Vector2D::new(3.0, 4.0);
            assert_eq!(v.magnitude(), 5.0);
    
            let sum = v.add(Vector2D::new(1.0, 1.0));
            assert_eq!(sum, Vector2D::new(4.0, 5.0));
            // v still valid after add (Copy)
            assert_eq!(v.magnitude(), 5.0);
        }
    
        #[test]
        fn test_labeled_point_clone() {
            let p1 = LabeledPoint::new(1.0, 2.0, "A");
            let p2 = p1.clone();
            assert_eq!(p1, p2);
            // Both are independent
            assert_eq!(p1.label, "A");
            assert_eq!(p2.label, "A");
        }
    
        #[test]
        fn test_labeled_point_with_label() {
            let p1 = LabeledPoint::new(1.0, 2.0, "original");
            let p2 = p1.with_label("modified");
            assert_eq!(p1.label, "original");
            assert_eq!(p2.label, "modified");
            // Coordinates are the same
            assert_eq!(p1.x, p2.x);
            assert_eq!(p1.y, p2.y);
        }
    
        #[test]
        fn test_dna_clone_independence() {
            let dna1 = DNA::new("ATCGATCG", "mouse");
            let mut dna2 = dna1.clone();
            dna2.mutate(0, 'G');
            assert_eq!(dna1.sequence, "ATCGATCG"); // Unchanged
            assert_eq!(dna2.sequence, "GTCGATCG"); // Mutated
        }
    
        #[test]
        fn test_dna_len() {
            let dna = DNA::new("ATCG", "test");
            assert_eq!(dna.len(), 4);
            assert!(!dna.is_empty());
        }
    
        #[test]
        fn test_color_copy() {
            let red = Color::rgb(255, 0, 0);
            let red2 = red; // Copy
            let red3 = red; // Still valid
            assert_eq!(red, red2);
            assert_eq!(red2, red3);
        }
    
        #[test]
        fn test_color_with_alpha() {
            let color = Color::rgb(100, 150, 200);
            let transparent = color.with_alpha(128);
            assert_eq!(color.a, 255); // Original unchanged (Copy)
            assert_eq!(transparent.a, 128);
        }
    
        #[test]
        fn test_color_blend() {
            let black = Color::rgb(0, 0, 0);
            let white = Color::rgb(255, 255, 255);
            let gray = black.blend(white, 0.5);
            assert_eq!(gray.r, 127);
            assert_eq!(gray.g, 127);
            assert_eq!(gray.b, 127);
        }
    
        #[test]
        fn test_copy_demonstration() {
            let (v1, v2) = copy_demonstration();
            assert_eq!(v1, v2);
        }
    
        #[test]
        fn test_clone_demonstration() {
            let (dna1, dna2) = clone_demonstration();
            assert_ne!(dna1.sequence, dna2.sequence);
            assert_eq!(dna1.sequence, "ATCG");
            assert_eq!(dna2.sequence, "GTCG");
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_vector_copy() {
            let v1 = Vector2D::new(1.0, 2.0);
            let v2 = v1; // Copy
            let v3 = v1; // Still valid
            assert_eq!(v1, v2);
            assert_eq!(v2, v3);
        }
    
        #[test]
        fn test_vector_operations() {
            let v = Vector2D::new(3.0, 4.0);
            assert_eq!(v.magnitude(), 5.0);
    
            let sum = v.add(Vector2D::new(1.0, 1.0));
            assert_eq!(sum, Vector2D::new(4.0, 5.0));
            // v still valid after add (Copy)
            assert_eq!(v.magnitude(), 5.0);
        }
    
        #[test]
        fn test_labeled_point_clone() {
            let p1 = LabeledPoint::new(1.0, 2.0, "A");
            let p2 = p1.clone();
            assert_eq!(p1, p2);
            // Both are independent
            assert_eq!(p1.label, "A");
            assert_eq!(p2.label, "A");
        }
    
        #[test]
        fn test_labeled_point_with_label() {
            let p1 = LabeledPoint::new(1.0, 2.0, "original");
            let p2 = p1.with_label("modified");
            assert_eq!(p1.label, "original");
            assert_eq!(p2.label, "modified");
            // Coordinates are the same
            assert_eq!(p1.x, p2.x);
            assert_eq!(p1.y, p2.y);
        }
    
        #[test]
        fn test_dna_clone_independence() {
            let dna1 = DNA::new("ATCGATCG", "mouse");
            let mut dna2 = dna1.clone();
            dna2.mutate(0, 'G');
            assert_eq!(dna1.sequence, "ATCGATCG"); // Unchanged
            assert_eq!(dna2.sequence, "GTCGATCG"); // Mutated
        }
    
        #[test]
        fn test_dna_len() {
            let dna = DNA::new("ATCG", "test");
            assert_eq!(dna.len(), 4);
            assert!(!dna.is_empty());
        }
    
        #[test]
        fn test_color_copy() {
            let red = Color::rgb(255, 0, 0);
            let red2 = red; // Copy
            let red3 = red; // Still valid
            assert_eq!(red, red2);
            assert_eq!(red2, red3);
        }
    
        #[test]
        fn test_color_with_alpha() {
            let color = Color::rgb(100, 150, 200);
            let transparent = color.with_alpha(128);
            assert_eq!(color.a, 255); // Original unchanged (Copy)
            assert_eq!(transparent.a, 128);
        }
    
        #[test]
        fn test_color_blend() {
            let black = Color::rgb(0, 0, 0);
            let white = Color::rgb(255, 255, 255);
            let gray = black.blend(white, 0.5);
            assert_eq!(gray.r, 127);
            assert_eq!(gray.g, 127);
            assert_eq!(gray.b, 127);
        }
    
        #[test]
        fn test_copy_demonstration() {
            let (v1, v2) = copy_demonstration();
            assert_eq!(v1, v2);
        }
    
        #[test]
        fn test_clone_demonstration() {
            let (dna1, dna2) = clone_demonstration();
            assert_ne!(dna1.sequence, dna2.sequence);
            assert_eq!(dna1.sequence, "ATCG");
            assert_eq!(dna2.sequence, "GTCG");
        }
    }

    Deep Comparison

    OCaml vs Rust: Clone and Copy Traits

    Side-by-Side Code

    OCaml — GC handles all copying

    (* All values can be copied implicitly *)
    let x = 42 in
    let y = x in  (* Both x and y are valid *)
    Printf.printf "%d %d\n" x y
    
    (* Explicit copy for mutable data *)
    let s1 = "hello" in
    let s2 = String.copy s1 in
    (* s1 and s2 are independent *)
    

    Rust — Copy vs Clone distinction

    // Copy: implicit, bitwise, for small stack types
    #[derive(Copy, Clone)]
    struct Point { x: i32, y: i32 }
    
    let p1 = Point { x: 1, y: 2 };
    let p2 = p1;  // Copy: p1 still valid
    println!("{} {}", p1.x, p2.x);
    
    // Clone: explicit, for heap-allocated types
    let s1 = String::from("hello");
    let s2 = s1.clone();  // Must be explicit
    // s1 and s2 are independent
    

    Comparison Table

    AspectOCamlRust
    DefaultGC-managed sharingMove semantics
    Implicit copyAll values (via GC)Only Copy types
    Explicit copyString.copy, etc..clone()
    Stack typesSame as heapCopy — implicit bitwise
    Heap typesSame as stackClone — explicit, may allocate
    PerformanceGC overheadZero-cost for Copy, explicit cost for Clone

    Copy Requirements

    A type can be Copy only if:

  • All fields are Copy
  • It doesn't implement Drop
  • It's entirely stack-based (no heap pointers)
  • // Can be Copy
    #[derive(Copy, Clone)]
    struct Point { x: i32, y: i32 }
    
    // Cannot be Copy (contains String → heap)
    #[derive(Clone)]
    struct Named { name: String, value: i32 }
    
    // Cannot be Copy (has Drop)
    struct Resource { /* ... */ }
    impl Drop for Resource { fn drop(&mut self) { } }
    

    Copy vs Clone in Practice

    // Copy: use after assignment
    let v1 = Vector2D { x: 1.0, y: 2.0 };
    let v2 = v1;  // Copy
    let v3 = v1;  // Still valid!
    assert_eq!(v1, v2);
    
    // Clone: explicit call required
    let dna1 = DNA::new("ATCG");
    let dna2 = dna1.clone();  // Explicit
    dna1.sequence;  // Still valid
    
    // Move (no Copy, no Clone)
    let s1 = String::from("hello");
    let s2 = s1;  // Moved
    // s1.len();  // Error: use of moved value
    

    5 Takeaways

  • OCaml's GC makes all values implicitly copyable.
  • No distinction between Copy and Clone.

  • Rust's Copy is implicit and zero-cost.
  • Bitwise copy for small, stack-only types.

  • Rust's Clone is explicit and may allocate.
  • Call .clone() to duplicate heap data.

  • Copy requires no Drop implementation.
  • Types with destructors cannot be Copy.

  • Default in Rust is move, not copy.
  • Assignment transfers ownership unless Copy is implemented.

    Exercises

  • Complex number: Implement Complex { re: f64, im: f64 } with Copy + Clone + Add + Mul + Display. Verify that both addition operands remain valid after the operation (due to Copy).
  • Color type: Create RgbColor { r: u8, g: u8, b: u8 } implementing Copy and LabeledColor { color: RgbColor, name: String } implementing only Clone. Write a function accepting impl Into<RgbColor> that shows RgbColor can be passed by value freely.
  • Clone cost measurement: Create a BigStruct { data: Vec<u8> } and clone it 1 million times. Then create a SmallStruct { x: f64, y: f64 } and copy it 1 million times. Measure and compare the time, explaining the difference.
  • Open Source Repos