languages/rust-ownership-why-let-b-a-makes-a-unusable.md
Rust ownership: why let b = a makes a unusable
Why does Rust refuse to compile `let b = a;` followed by a use of `a`? Because of ownership: every value has exactly one owner, and assigning a `String` to a new variable moves it.
Step through five tiny programs (a move, plain numbers, `clone`, a borrow with `&`, and passing a value to a function) and watch which variable owns the heap text at each line. You will also see the real compiler errors from Rust 1.97, and learn what to do when you meet one.

Here is a four-line Rust program. In many languages it would be fine. In Rust it refuses to build:
fn main() {
let a = String::from("hi");
let b = a;
println!("{}", a); // error: borrow of moved value: `a`
}
Why would Rust refuse to let you read a just because you wrote let b = a? The answer is the idea Rust is best known for: ownership. It takes a few minutes to see, and once you do, the strange error messages start to read like helpful advice.
What a String really is
A variable in a program lives in one of two places. The stack is a tidy, fast area where each function keeps its small, fixed-size variables. The heap is a bigger, looser area for data whose size can change, like text a user typed. A Rust String uses both: on the stack it is a small label holding a pointer (an address), a length and a capacity, and the actual letters sit on the heap at that address.
So a is not the text hi itself. It is a small label that points to the text.
The problem ownership solves
Heap memory has to be given back when you are done with it, or the program slowly eats all the memory. Now imagine let b = a copied only the label. Both a and b would point at the same letters. When the function ends, both would try to give that memory back. Freeing the same memory twice is called a double free, and in languages like C it is a classic source of crashes and security holes.
Rust avoids this with three rules:
- Every value has an owner.
- There can be only one owner at a time.
- When the owner goes out of scope (its function or block ends), the value is dropped, meaning its memory is freed.
With one owner there is exactly one cleanup. So let b = a does not make two owners. It moves ownership from a to b, and a becomes unusable.
Try it: watch ownership move
One honest detail about the figure: Rust checks these rules before your program runs. When a program breaks them, the compiler stops and nothing runs at all. The stepping in the broken programs shows what the compiler is keeping track of, not something that happens on your computer.
Why numbers behave differently
Look at the second program in the figure. let y = x with x = 5 leaves both variables usable. That is because an integer lives entirely on the stack. There is no heap memory to free twice, so copying it is cheap and safe, and Rust simply makes an independent copy. Types that work like this are called Copy types: integers, bool, floating-point numbers, char, and tuples made only of those. A String is not Copy because it owns heap memory.
Three ways out
When you want to keep using a, you have three honest options.
1. Clone: make a real second copy
let a = String::from("hi");
let b = a.clone();
println!("{} {}", a, b);
Output when I ran it: hi hi. clone copies the letters on the heap too, so there are two owners of two separate values. It costs time and memory, which is why Rust makes you ask for it out loud.
2. Borrow: lend it without giving it away
fn shout(s: &String) -> usize {
println!("{}!", s);
s.len()
}
fn main() {
let a = String::from("hi");
let n = shout(&a);
println!("{} still here, len {}", a, n);
}
Output when I ran it:
hi!
hi still here, len 2
The & means "let this function look at it". The function gets a reference, and ownership stays with a. (Rust programmers more often write &str for a parameter like this. That is a small refinement for later.)
3. Move on purpose
Passing a into a function by value, as in eat(a), moves it there. The function becomes the owner, and when it ends the value is dropped. That is a fine choice when the function really should consume the value, and the fourth program in the figure shows what happens if you read a afterwards.
So what, for your own code
When you meet borrow of moved value, read the message from the top. The compiler points to three lines: where the value was created, where it moved, and where you used it too late. It also suggests a fix, often .clone(). Before you accept that, ask whether the function only needs to look at the value. If it does, a borrow with & is usually the better answer.
The rules feel strict at first, but they buy something real: no double frees, and no reading memory that was already freed, with no garbage collector running behind your back. The checks above were run with Rust 1.97 (2026). The wording of error messages changes a little between versions, but the rules themselves do not.