Skip to content
Select theme

How to understand ownership if you come from Python or JavaScript

Short answer: in Python and JavaScript, a variable is a name for an object, and many names can point at the same one. In Rust, a binding is the owner of its value, and let b = a; hands that ownership over instead of binding a second name. Most early ownership errors come from using that second name.

In Python, b = a doesn’t copy the list. Both names reach the same object, a change through either is visible through the other, and it lives until a garbage collector finds nothing reaching it. JavaScript is the same for objects and arrays. Integers and strings never reveal the sharing, but the model underneath is identical, and it has no place for a name retired by an assignment.

let b = a; moves. For a String, Vec, or Box, ownership transfers to b, and a isn’t emptied or freed — it’s retired, and the next line that mentions it is a compile error. What happens to the original variable after a move? covers that state. The reason is the double free: two names owning one heap buffer would each free it, so the compiler won’t leave two owners in play (Why does Rust move values?).

The types that behave like Python’s integers are the Copy ones — i32, f64, bool, char, references — and let b = a; leaves both usable; how to tell whether a type will move or copy is the test for the rest.

Calling a function. f(x) never takes anything from the caller. A by-value Rust parameter takes ownership, and the caller’s binding dies at the call. Borrowing matches your intuition: f(&x) hands over a reference, and you keep the value. That & is the shape of most Rust signatures — passing a Vec to a function without cloning it is the worked example.

Looping. for item in vec consumes the Vec: a for loop calls into_iter, which takes its receiver by value. for item in &vec is the loop you were expecting (looping over a Vec without moving it).

Storing. Pushing your value into a collection moves it in; the clone at the push is the price of a second copy, and When should you clone? sorts out when to pay it. Rc (and Arc across threads) is the sharing answer, in the smart pointers book.

Mutating. Any name for an object can change it in JavaScript. Rust allows mutation through &mut only, exclusively: while the borrow exists, nothing else may touch the value.

A collector frees the object when nothing can reach it; Rust drops the value at the closing brace of the owning scope, before the next statement, every time. How does Rust manage memory without a garbage collector? works through what that buys.

Python / JavaScript habit What Rust does instead
b = a binds a second name let b = a; moves, unless the type is Copy
f(x) shares the argument by-value parameter moves; f(&x) borrows
for x in items leaves it alone by-value loop consumes; &items borrows
any name can mutate &mut only, and exclusively
freed when unreachable dropped at the owner’s closing brace