Move vs Copy
JavaScript’s blurry line between value and reference
Section titled “JavaScript’s blurry line between value and reference”In JavaScript, primitives (number, boolean, string) are passed by value — copying them is cheap and automatic. Objects and arrays are passed by reference — both variables point to the same heap data.
TypeScript exposes this through the type system but does not give you control over it. You cannot make your own type behave like a primitive.
Rust is explicit: types either implement the Copy trait (and are automatically duplicated on assignment) or they follow move semantics (ownership transfers). You can also derive Clone for an explicit, deep copy.
Copy types — stack values that duplicate automatically
Section titled “Copy types — stack values that duplicate automatically”Copy types are stack-allocated and cheap to duplicate. Assigning or passing them creates a full independent copy with no extra syntax needed.
// TypeScript — primitives copy automatically (same as Rust Copy)let a = 42;let b = a; // b is an independent copyb = 100;console.log(a); // 42 — a is unchanged
// But TypeScript has no way to make a struct behave like this:const p1 = { x: 1, y: 2 };const p2 = p1; // p2 is a reference — they share the same objectp2.x = 99;console.log(p1.x); // 99 — p1 was mutated through p2fn main() { // i32, f64, bool, char are all Copy let a: i32 = 42; let b = a; // copied — a is still valid println!("a={} b={}", a, b);
// Tuples of Copy types are also Copy let t1 = (1, 2.0, true); let t2 = t1; println!("t1={:?} t2={:?}", t1, t2);}Move types — heap values that transfer ownership
Section titled “Move types — heap values that transfer ownership”Types like String and Vec<T> own heap-allocated data. Assigning them moves ownership. If you need two independent copies, you call .clone() explicitly.
// TypeScript — strings are primitives, always copiedlet s1 = "hello";let s2 = s1;console.log(s1, s2); // both valid — strings are primitives in JS
// Objects require spread for a shallow copyconst obj1 = { items: [1, 2, 3] };const obj2 = { ...obj1 }; // shallow cloneconst obj3 = structuredClone(obj1); // deep clone (ES2022)console.log(obj1.items, obj2.items);#[derive(Debug, Clone)]struct Point { x: f64, y: f64,}
fn main() { // String — heap value, uses move semantics let s1 = String::from("hello"); let s2 = s1.clone(); // explicit deep copy println!("s1={} s2={}", s1, s2); // both valid
// Your own struct — derive Clone to get .clone() let p1 = Point { x: 1.0, y: 2.0 }; let p2 = p1.clone(); println!("p1={:?} p2={:?}", p1, p2);
// Without Clone, assignment moves ownership: let p3 = Point { x: 3.0, y: 4.0 }; let p4 = p3; // p3 is moved — p3 is no longer valid println!("p4={:?}", p4);}Try it
Section titled “Try it”#[derive(Debug, Clone)]struct Point { x: f64, y: f64,}
fn main() { let a: i32 = 42; let b = a; println!("a={} b={} (both valid, i32 is Copy)", a, b);
let p1 = Point { x: 1.0, y: 2.0 }; let p2 = p1.clone(); println!("p1={:?} p2={:?}", p1, p2);
let s1 = String::from("hello"); let s2 = s1.clone(); println!("s1={} s2={}", s1, s2);}Compiling…