408: Clone and Copy Traits
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
Copy = implicit bit copy; Clone = explicit potentially-expensive copyCopy (no heap allocation, no Drop, all fields Copy)Vector2D: Copy allows passing by value without move semanticsLabeledPoint (contains String) can only be Clone, not Copy#[derive(Clone, Copy)] works and its requirementsCode 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 independentKey Differences
Copy types copy implicitly on assignment; OCaml copies are always by reference (shared) unless explicitly duplicated.Copy exclusion of heap types prevents accidental O(n) copies; OCaml relies on GC reference counting to manage copies safely.Drop cannot implement Copy (drop semantics conflict with bitwise copy); OCaml's GC finalizers have no such restriction..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");
}
}#[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
| Aspect | OCaml | Rust |
|---|---|---|
| Default | GC-managed sharing | Move semantics |
| Implicit copy | All values (via GC) | Only Copy types |
| Explicit copy | String.copy, etc. | .clone() |
| Stack types | Same as heap | Copy — implicit bitwise |
| Heap types | Same as stack | Clone — explicit, may allocate |
| Performance | GC overhead | Zero-cost for Copy, explicit cost for Clone |
Copy Requirements
A type can be Copy only if:
CopyDrop// 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
No distinction between Copy and Clone.
Bitwise copy for small, stack-only types.
Call .clone() to duplicate heap data.
Types with destructors cannot be Copy.
Assignment transfers ownership unless Copy is implemented.
Exercises
Complex { re: f64, im: f64 } with Copy + Clone + Add + Mul + Display. Verify that both addition operands remain valid after the operation (due to Copy).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.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.