ExamplesBy LevelBy TopicLearning Paths
896 Intermediate

896-clone-copy — Clone and Copy Traits

Functional Programming

Tutorial

The Problem

Rust's ownership model means that values are moved by default. For types where copying is cheap and always valid — integers, booleans, simple structs — implicit copying is desirable. For types with heap allocation — String, Vec, complex structs — copying is potentially expensive and must be explicit. Rust encodes this distinction in two traits: Copy (implicit bitwise copy, zero-cost) and Clone (explicit .clone(), potentially expensive). Seeing .clone() in code signals a potentially costly heap duplication. Not seeing it means the copy is stack-only. This visual distinction makes performance characteristics readable from the code.

🎯 Learning Outcomes

  • • Understand the semantic difference between Copy (implicit) and Clone (explicit)
  • • Derive Copy and Clone for simple structs and recognize when Copy is appropriate
  • • Understand why String, Vec, and other heap-owning types cannot be Copy
  • • Recognize .clone() as a visible signal of heap allocation in code reviews
  • • Compare with OCaml's uniform representation where all values are implicitly shareable
  • Code Example

    #[derive(Debug, Copy, Clone, PartialEq)]
    pub struct Point { pub x: f64, pub y: f64 }
    
    impl Point {
        pub fn translate(self, dx: f64, dy: f64) -> Self {
            Self { x: self.x + dx, y: self.y + dy }
        }
    }
    
    fn copy_demo() {
        let origin = Point { x: 0.0, y: 0.0 };
        let moved = origin.translate(1.0, 2.0); // origin copied implicitly
        assert_eq!(origin, Point { x: 0.0, y: 0.0 }); // still valid
        assert_eq!(moved.x, 1.0);
    
        let x: i32 = 42;
        let y = x; // silent bitwise copy — both x and y are valid
        assert_eq!(x, y);
    }

    Key Differences

  • Implicit vs explicit: Rust Copy types copy silently; Clone types require .clone(). OCaml passes a shared pointer always — no per-value choice.
  • Performance visibility: In Rust, .clone() signals potential allocation; in OCaml, copying is never visible in the source — profiling is required to detect it.
  • Composability: A struct is Copy only if all its fields are Copy; OCaml values are always shareable regardless of field types.
  • Heap types: String, Vec, Box cannot be Copy (they own heap memory); in OCaml, all heap-allocated values are shareable via GC.
  • OCaml Approach

    OCaml has no Copy/Clone distinction. All values (including strings and lists) are implicitly sharable — passing a value to a function passes a pointer, not a copy. Immutability makes sharing safe: OCaml strings used to be mutable (a historical mistake); Bytes.t is now the mutable string type. For actual copying: String.copy (deprecated), Bytes.copy, Array.copy. OCaml's structural sharing means no distinction between "move" and "share" — the GC handles it.

    Full Source

    #![allow(clippy::all)]
    // Example 896: Clone and Copy Traits
    //
    // Copy: bitwise copy, implicit, for simple stack types (i32, f64, bool, tuples of Copy, etc.)
    // Clone: explicit deep copy via .clone(), for heap-owning types (String, Vec, etc.)
    //
    // The key insight: seeing `.clone()` in code signals a potentially expensive heap allocation.
    // Not seeing it means the copy is cheap (stack-only). No hidden costs.
    
    // --- Copy types ---
    
    /// Demonstrates Copy semantics: assignment silently duplicates the value.
    /// Both the original and the copy are independently valid after assignment.
    pub fn copy_integer(x: i32) -> (i32, i32) {
        let y = x; // bitwise copy — x is still valid
        (x, y)
    }
    
    /// Tuples of Copy types are themselves Copy.
    pub fn copy_tuple(p: (f64, f64), dx: f64, dy: f64) -> (f64, f64) {
        let q = p; // p is copied, both remain valid
        (q.0 + dx, q.1 + dy)
    }
    
    /// A simple point struct that derives both Copy and Clone.
    /// Copy enables silent duplication; Clone enables `.clone()` calls.
    #[derive(Debug, Copy, Clone, PartialEq)]
    pub struct Point {
        pub x: f64,
        pub y: f64,
    }
    
    impl Point {
        pub fn new(x: f64, y: f64) -> Self {
            Self { x, y }
        }
    
        /// Returns a new translated point — original is preserved via Copy.
        pub fn translate(self, dx: f64, dy: f64) -> Self {
            Self {
                x: self.x + dx,
                y: self.y + dy,
            }
        }
    }
    
    // --- Clone types ---
    
    /// Demonstrates Clone semantics: explicit `.clone()` required for heap-owning types.
    /// After `s2 = s1.clone()`, both strings are independent heap allocations.
    pub fn clone_string(s: &str) -> (String, String) {
        let s1 = s.to_owned();
        let s2 = s1.clone(); // explicit — you see the cost
        (s1, s2)
    }
    
    /// Clones a Vec: each clone is a fresh heap allocation with copied elements.
    pub fn clone_vec(v: &[i32]) -> (Vec<i32>, Vec<i32>) {
        let v1 = v.to_vec();
        let v2 = v1.clone(); // explicit deep copy
        (v1, v2)
    }
    
    /// Shows that modifying a cloned value does not affect the original.
    pub fn independent_after_clone(original: &str) -> (String, String) {
        let s1 = original.to_owned();
        let mut s2 = s1.clone();
        s2.push_str(" (copy)");
        (s1, s2)
    }
    
    // --- Mixed: a struct that owns heap data, so only Clone (not Copy) ---
    
    /// A named point that owns its label string — cannot be Copy, only Clone.
    #[derive(Debug, Clone, PartialEq)]
    pub struct NamedPoint {
        pub label: String,
        pub x: f64,
        pub y: f64,
    }
    
    impl NamedPoint {
        pub fn new(label: &str, x: f64, y: f64) -> Self {
            Self {
                label: label.to_owned(),
                x,
                y,
            }
        }
    
        pub fn translate(&self, dx: f64, dy: f64) -> Self {
            Self {
                label: self.label.clone(), // must clone the String
                x: self.x + dx,
                y: self.y + dy,
            }
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_copy_integer_both_valid() {
            let (x, y) = copy_integer(42);
            assert_eq!(x, 42);
            assert_eq!(y, 42);
        }
    
        #[test]
        fn test_copy_tuple_independence() {
            let p = (1.0_f64, 2.0_f64);
            let translated = copy_tuple(p, 3.0, 4.0);
            // original p is still (1.0, 2.0) — Copy means independent
            assert_eq!(p, (1.0, 2.0));
            assert_eq!(translated, (4.0, 6.0));
        }
    
        #[test]
        fn test_copy_struct_translate_preserves_original() {
            let origin = Point::new(0.0, 0.0);
            let moved = origin.translate(1.0, 2.0);
            // origin unchanged — Point is Copy
            assert_eq!(origin, Point::new(0.0, 0.0));
            assert_eq!(moved, Point::new(1.0, 2.0));
        }
    
        #[test]
        fn test_clone_string_independence() {
            let (s1, s2) = clone_string("hello");
            assert_eq!(s1, "hello");
            assert_eq!(s2, "hello");
            // they are equal but independent allocations
            assert_eq!(s1, s2);
        }
    
        #[test]
        fn test_clone_vec_independence() {
            let (v1, v2) = clone_vec(&[1, 2, 3]);
            assert_eq!(v1, vec![1, 2, 3]);
            assert_eq!(v2, vec![1, 2, 3]);
        }
    
        #[test]
        fn test_independent_after_clone_no_aliasing() {
            let (original, copy) = independent_after_clone("hello");
            assert_eq!(original, "hello");
            assert_eq!(copy, "hello (copy)");
            // modifying the clone did not affect the original
            assert_ne!(original, copy);
        }
    
        #[test]
        fn test_named_point_clone_and_translate() {
            let np = NamedPoint::new("origin", 0.0, 0.0);
            let moved = np.translate(5.0, -3.0);
            // np is still valid — we borrowed it in translate
            assert_eq!(np.label, "origin");
            assert_eq!(np.x, 0.0);
            assert_eq!(moved.label, "origin");
            assert_eq!(moved.x, 5.0);
            assert_eq!(moved.y, -3.0);
        }
    
        #[test]
        fn test_named_point_clone_is_independent() {
            let np1 = NamedPoint::new("point", 1.0, 2.0);
            let mut np2 = np1.clone();
            np2.label = "clone".to_owned();
            assert_eq!(np1.label, "point"); // original unaffected
            assert_eq!(np2.label, "clone");
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_copy_integer_both_valid() {
            let (x, y) = copy_integer(42);
            assert_eq!(x, 42);
            assert_eq!(y, 42);
        }
    
        #[test]
        fn test_copy_tuple_independence() {
            let p = (1.0_f64, 2.0_f64);
            let translated = copy_tuple(p, 3.0, 4.0);
            // original p is still (1.0, 2.0) — Copy means independent
            assert_eq!(p, (1.0, 2.0));
            assert_eq!(translated, (4.0, 6.0));
        }
    
        #[test]
        fn test_copy_struct_translate_preserves_original() {
            let origin = Point::new(0.0, 0.0);
            let moved = origin.translate(1.0, 2.0);
            // origin unchanged — Point is Copy
            assert_eq!(origin, Point::new(0.0, 0.0));
            assert_eq!(moved, Point::new(1.0, 2.0));
        }
    
        #[test]
        fn test_clone_string_independence() {
            let (s1, s2) = clone_string("hello");
            assert_eq!(s1, "hello");
            assert_eq!(s2, "hello");
            // they are equal but independent allocations
            assert_eq!(s1, s2);
        }
    
        #[test]
        fn test_clone_vec_independence() {
            let (v1, v2) = clone_vec(&[1, 2, 3]);
            assert_eq!(v1, vec![1, 2, 3]);
            assert_eq!(v2, vec![1, 2, 3]);
        }
    
        #[test]
        fn test_independent_after_clone_no_aliasing() {
            let (original, copy) = independent_after_clone("hello");
            assert_eq!(original, "hello");
            assert_eq!(copy, "hello (copy)");
            // modifying the clone did not affect the original
            assert_ne!(original, copy);
        }
    
        #[test]
        fn test_named_point_clone_and_translate() {
            let np = NamedPoint::new("origin", 0.0, 0.0);
            let moved = np.translate(5.0, -3.0);
            // np is still valid — we borrowed it in translate
            assert_eq!(np.label, "origin");
            assert_eq!(np.x, 0.0);
            assert_eq!(moved.label, "origin");
            assert_eq!(moved.x, 5.0);
            assert_eq!(moved.y, -3.0);
        }
    
        #[test]
        fn test_named_point_clone_is_independent() {
            let np1 = NamedPoint::new("point", 1.0, 2.0);
            let mut np2 = np1.clone();
            np2.label = "clone".to_owned();
            assert_eq!(np1.label, "point"); // original unaffected
            assert_eq!(np2.label, "clone");
        }
    }

    Deep Comparison

    OCaml vs Rust: Clone and Copy

    Side-by-Side Code

    OCaml

    (* OCaml: all values are implicitly shared/copied by the GC.
       No distinction between cheap stack copy and expensive heap copy. *)
    
    type point = { x : float; y : float }
    
    let translate p dx dy = { x = p.x +. dx; y = p.y +. dy }
    
    let () =
      let origin = { x = 0.0; y = 0.0 } in
      let moved = translate origin 1.0 2.0 in
      (* origin is still valid — GC manages both records implicitly *)
      assert (origin.x = 0.0);
      assert (moved.x = 1.0);
    
      (* Strings: structural sharing, no explicit clone needed *)
      let s1 = "hello" in
      let s2 = s1 ^ " world" in   (* creates new string, GC handles old *)
      assert (s1 = "hello");
      assert (s2 = "hello world");
      print_endline "ok"
    

    Rust (Copy — stack types, implicit duplication)

    #[derive(Debug, Copy, Clone, PartialEq)]
    pub struct Point { pub x: f64, pub y: f64 }
    
    impl Point {
        pub fn translate(self, dx: f64, dy: f64) -> Self {
            Self { x: self.x + dx, y: self.y + dy }
        }
    }
    
    fn copy_demo() {
        let origin = Point { x: 0.0, y: 0.0 };
        let moved = origin.translate(1.0, 2.0); // origin copied implicitly
        assert_eq!(origin, Point { x: 0.0, y: 0.0 }); // still valid
        assert_eq!(moved.x, 1.0);
    
        let x: i32 = 42;
        let y = x; // silent bitwise copy — both x and y are valid
        assert_eq!(x, y);
    }
    

    Rust (Clone — heap types, explicit deep copy)

    fn clone_demo() {
        let s1 = String::from("hello");
        let s2 = s1.clone(); // explicit — you see the heap allocation cost
        assert_eq!(s1, s2);  // s1 still valid, s2 is a new allocation
    
        let v1 = vec![1, 2, 3];
        let v2 = v1.clone(); // another explicit deep copy
        assert_eq!(v1, v2);
    }
    

    Rust (Clone-only struct — owns heap data)

    #[derive(Debug, Clone, PartialEq)]
    pub struct NamedPoint {
        pub label: String, // owns heap data → cannot be Copy
        pub x: f64,
        pub y: f64,
    }
    
    impl NamedPoint {
        pub fn translate(&self, dx: f64, dy: f64) -> Self {
            Self {
                label: self.label.clone(), // must clone the String explicitly
                x: self.x + dx,
                y: self.y + dy,
            }
        }
    }
    

    Type Signatures

    ConceptOCamlRust
    Cheap copyImplicit (GC handles everything)Copy trait — bitwise, silent
    Expensive copyImplicit (GC allocates new heap object)Clone trait — explicit .clone() call
    Stack-only structtype point = { x: float; y: float }#[derive(Copy, Clone)] struct Point
    Heap-owning structtype named = { label: string; x: float }#[derive(Clone)] struct NamedPoint { label: String, .. }
    String copylet s2 = s1 (shared/copied implicitly)let s2 = s1.clone() (explicit heap allocation)
    Function taking structlet f p = ... (implicit copy or share)fn f(p: Point) (moved or copied, depending on Copy)

    Key Insights

  • Visibility of cost: In OCaml the GC silently manages all copies and sharing — you never see the cost. In Rust, Copy (cheap, stack-only) is invisible while Clone (potentially expensive, heap-allocating) must be written explicitly, making costs visible at the call site.
  • **Copy is a subset of Clone**: A Copy type must also implement Clone; Clone is the general interface, Copy is the optimized silent variant. If a type contains any non-Copy field (like String), it cannot be Copy, only Clone.
  • Move semantics vs copy semantics: Types without Copy are moved on assignment — the original binding becomes invalid. Types with Copy are silently duplicated. OCaml has neither concept because the GC tracks all references.
  • Design signal: When you see .clone() in a Rust code review, it is a deliberate signal — something heap-heavy is being duplicated. In OCaml you never get that signal, which can hide performance problems.
  • Struct composition determines trait availability: Adding a single String field to an otherwise stack-only struct removes the ability to derive Copy. This makes the memory model explicit in the type definition itself, not just at use sites.
  • When to Use Each Style

    **Derive Copy when:** the struct holds only stack-sized, trivially-copyable fields (i32, f64, bool, arrays of Copy, etc.) and implicit duplication is semantically correct (e.g., coordinate types, small value objects).

    **Derive only Clone when:** the struct owns heap data (String, Vec, Box, etc.) and copies must be explicit to make their cost visible to the reader.

    Exercises

  • Create a Matrix2x2 struct with four f64 fields, derive Copy + Clone, and implement matrix multiplication returning a new matrix.
  • Write a function cloned_pipeline(data: &[String]) -> Vec<String> that clones elements conditionally — only those passing a filter — and explain why .clone() is needed.
  • Implement a Config struct with String fields and #[derive(Clone)], then write apply_override(base: &Config, override: Config) -> Config using struct update syntax.
  • Open Source Repos