A strong answer
In Rust, every value has a single owner responsible for freeing its resources when the owner leaves scope (Drop::drop). A String (or VecT``) is a 24-byte header on the stack (ptr: *mut u8, len: usize, cap: usize, 8 bytes each) pointing to a heap buffer: (1) If let s2 = s1 left both s1 and s2 active pointing to the same heap buffer (0x5000), then when the function returned, both s1 and s2 would call dealloc(0x5000)—causing a catastrophic double-free vulnerability! (2) Instead of implicitly deep-copying on = (slow) or reference counting (runtime overhead), Rust performs a Move: it copies the 24-byte header (ptr, len, cap) on the stack into s2 in O(1) time (0 heap allocations!) and statically marks s1 as uninitialized in the compiler's control-flow graph! Types stored purely on the stack with no heap/resource cleanup (i32, f64, bool, shared references &T) implement the marker trait Copy, so let x2 = x1 copies the bits and keeps x1 valid.
At every program point, each heap buffer owned by a String, VecT, or `Box`T has exactly one live initialized owner variable; when that owner exits scope (and has not been moved), the compiler inserts exactly one Drop::drop call. Because a move transfers ownership from source $v_1$ to destination $v_2$ and marks $v_1$ uninitialized in the compiler's MIR borrow/move checker (emitting a drop_flag only when moves are conditional across branches), every owned allocation is freed exactly once (#allocs == #drops) with zero use-after-move or double-free.
Moving a 1-gigabyte Vec<u8> or String across functions in Rust copies only 24 bytes on the stack (~1 ns, 0 heap allocations, 0 atomic reference-count instructions). In production systems (Every Rust standard library container (Vec, String, Box, HashMap), Linux kernel Rust drivers, Cloudflare Pingora, and AWS Firecracker rely on compile-time ownership and deterministic Drop instead of garbage collection.), engineers monitor these exact trade-offs to keep p99 latency predictable and prevent resource exhaustion under peak load.
What the interviewer is listening for
- Traces how Rust ownership, bitwise stack moves, and deterministic
Dropoperates in memory step by step rather than giving a vague textbook definition. - States the core correctness invariant and explains why edge cases do not violate it.
- Quantifies time, space, and hardware costs (Move (
let s2 = s1), Clone (let s2 = s1.clone())) with concrete numbers. - Identifies real-world failure modes (Calling
.clone()on every function argument just to satisfy the compiler) and explains how production systems mitigate them.
Common mistakes
- Treating Rust ownership, bitwise stack moves, and deterministic
Dropas a black box without explaining the underlying memory or state transitions. - Ignoring worst-case tail latency or memory overhead (Writing
func process(s: &String)orfunc sum(v: &Vec<i32>)in function signatures). - Forgetting boundary conditions and error recovery (Expecting
drop(mutex_guard)to happen mid-scope without an explicitdrop(guard)or inner{}block).