ExamplesBy LevelBy TopicLearning Paths
102 Intermediate

102-clone-copy — Clone vs Copy

Functional Programming

Tutorial

The Problem

Copying data efficiently is fundamental to both performance and memory safety. C++ distinguishes copy constructors from move constructors. Rust makes the distinction even sharper: the Copy trait marks types for which a bitwise duplicate is always safe and cheap (integers, booleans, small structs with no heap pointers), while Clone is the explicit mechanism for deep copies that may allocate.

This distinction has a practical impact: if you pass a Copy type to a function, the original remains usable. If you pass a non-Copy type, it is moved. This drives API design decisions about which types should derive Copy.

🎯 Learning Outcomes

  • • Understand the Copy trait and which types implement it
  • • Use .clone() explicitly for heap-allocated types
  • • Know why String is not Copy but &str is Copy
  • • Recognise the performance implications of deep clone versus shallow copy
  • • Design types that intentionally are or are not Copy
  • Code Example

    #![allow(clippy::all)]
    // 102: Clone vs Copy
    // Copy = implicit bitwise copy (small stack types)
    // Clone = explicit deep copy (heap types)
    
    // Copy types: integers, floats, bool, char, tuples of Copy types
    #[derive(Debug, Clone, Copy, PartialEq)]
    struct Point {
        x: f64,
        y: f64,
    }
    
    // Clone-only types: anything with heap allocation
    #[derive(Debug, Clone, PartialEq)]
    struct Person {
        name: String,
        age: u32,
    }
    
    fn demonstrate_copy() {
        let p1 = Point { x: 1.0, y: 2.0 };
        let p2 = p1; // Copy — p1 still valid
        assert_eq!(p1, p2);
        // Both usable!
        println!("p1: {:?}, p2: {:?}", p1, p2);
    }
    
    fn demonstrate_clone() {
        let p1 = Person {
            name: "Alice".into(),
            age: 30,
        };
        let p2 = p1.clone(); // explicit deep copy
                             // p1 is still valid because we cloned
        assert_eq!(p1, p2);
        println!("p1: {:?}", p1);
    
        let p3 = p1; // move — p1 no longer valid
                     // println!("{:?}", p1); // ERROR!
        println!("p3: {:?}", p3);
    }
    
    fn demonstrate_vec_clone() {
        let v1 = vec![1, 2, 3];
        let v2 = v1.clone(); // deep copy
        assert_eq!(v1, v2);
        // v1 still valid because we cloned
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_copy() {
            let a = 42;
            let b = a; // Copy
            assert_eq!(a, b);
        }
    
        #[test]
        fn test_point_copy() {
            let p1 = Point { x: 1.0, y: 2.0 };
            let p2 = p1;
            assert_eq!(p1.x, p2.x); // both valid
        }
    
        #[test]
        fn test_clone() {
            let s1 = String::from("hello");
            let s2 = s1.clone();
            assert_eq!(s1, s2); // both valid
        }
    
        #[test]
        fn test_vec_clone() {
            let v1 = vec![1, 2, 3];
            let v2 = v1.clone();
            assert_eq!(v1, v2);
        }
    }

    Key Differences

  • Implicit vs explicit: OCaml copies are always implicit (aliases via GC); Rust requires explicit .clone() for heap types, making allocation visible.
  • **Copy marker**: Rust's Copy is a compiler-enforced opt-in; OCaml has no equivalent — all types are freely assignable.
  • Mutable state: In OCaml, aliasing a mutable value (like a ref) means both names see mutations; in Rust, Clone creates an independent copy with no shared state.
  • Performance visibility: Rust's explicit .clone() makes heap allocation visible at call sites; OCaml hides GC costs.
  • OCaml Approach

    OCaml has no explicit copy/clone distinction. All values are either unboxed (integers, booleans) or heap-allocated with GC-managed sharing. Assigning a value in OCaml always creates an alias — both bindings point to the same heap node. Deep copying requires explicit library functions:

    let deep_copy_list lst = List.map Fun.id lst
    let deep_copy_array arr = Array.copy arr
    

    The GC handles memory without requiring the programmer to track who owns what.

    Full Source

    #![allow(clippy::all)]
    // 102: Clone vs Copy
    // Copy = implicit bitwise copy (small stack types)
    // Clone = explicit deep copy (heap types)
    
    // Copy types: integers, floats, bool, char, tuples of Copy types
    #[derive(Debug, Clone, Copy, PartialEq)]
    struct Point {
        x: f64,
        y: f64,
    }
    
    // Clone-only types: anything with heap allocation
    #[derive(Debug, Clone, PartialEq)]
    struct Person {
        name: String,
        age: u32,
    }
    
    fn demonstrate_copy() {
        let p1 = Point { x: 1.0, y: 2.0 };
        let p2 = p1; // Copy — p1 still valid
        assert_eq!(p1, p2);
        // Both usable!
        println!("p1: {:?}, p2: {:?}", p1, p2);
    }
    
    fn demonstrate_clone() {
        let p1 = Person {
            name: "Alice".into(),
            age: 30,
        };
        let p2 = p1.clone(); // explicit deep copy
                             // p1 is still valid because we cloned
        assert_eq!(p1, p2);
        println!("p1: {:?}", p1);
    
        let p3 = p1; // move — p1 no longer valid
                     // println!("{:?}", p1); // ERROR!
        println!("p3: {:?}", p3);
    }
    
    fn demonstrate_vec_clone() {
        let v1 = vec![1, 2, 3];
        let v2 = v1.clone(); // deep copy
        assert_eq!(v1, v2);
        // v1 still valid because we cloned
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_copy() {
            let a = 42;
            let b = a; // Copy
            assert_eq!(a, b);
        }
    
        #[test]
        fn test_point_copy() {
            let p1 = Point { x: 1.0, y: 2.0 };
            let p2 = p1;
            assert_eq!(p1.x, p2.x); // both valid
        }
    
        #[test]
        fn test_clone() {
            let s1 = String::from("hello");
            let s2 = s1.clone();
            assert_eq!(s1, s2); // both valid
        }
    
        #[test]
        fn test_vec_clone() {
            let v1 = vec![1, 2, 3];
            let v2 = v1.clone();
            assert_eq!(v1, v2);
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_copy() {
            let a = 42;
            let b = a; // Copy
            assert_eq!(a, b);
        }
    
        #[test]
        fn test_point_copy() {
            let p1 = Point { x: 1.0, y: 2.0 };
            let p2 = p1;
            assert_eq!(p1.x, p2.x); // both valid
        }
    
        #[test]
        fn test_clone() {
            let s1 = String::from("hello");
            let s2 = s1.clone();
            assert_eq!(s1, s2); // both valid
        }
    
        #[test]
        fn test_vec_clone() {
            let v1 = vec![1, 2, 3];
            let v2 = v1.clone();
            assert_eq!(v1, v2);
        }
    }

    Deep Comparison

    Core Insight

    Copy is implicit bitwise copy for small types; Clone is explicit deep copy — Rust makes the cost visible

    OCaml Approach

  • • See example.ml for implementation
  • Rust Approach

  • • See example.rs for implementation
  • Comparison Table

    FeatureOCamlRust
    Seeexample.mlexample.rs

    Exercises

  • Create a Matrix2x2([[f64; 2]; 2]) struct. Explain why it can derive Copy even though it contains an array, and verify by using it after a move.
  • Write a function that takes a Vec<String> by value, clones each element, and returns a Vec<String> with all strings uppercased — keeping the original unmodified.
  • Design a Handle(u64) newtype that intentionally does NOT derive Copy to force explicit ownership management. Demonstrate the compile error when trying to use it after a move.
  • Open Source Repos