102-clone-copy — Clone vs Copy
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
Copy trait and which types implement it.clone() explicitly for heap-allocated typesString is not Copy but &str is CopyCopyCode 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
.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.ref) means both names see mutations; in Rust, Clone creates an independent copy with no shared state..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);
}
}#[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
Rust Approach
Comparison Table
| Feature | OCaml | Rust |
|---|---|---|
| See | example.ml | example.rs |
Exercises
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.Vec<String> by value, clones each element, and returns a Vec<String> with all strings uppercased — keeping the original unmodified.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.