Most languages let you keep using a variable after you've assigned it somewhere else. Rust usually doesn't, and it stops you at compile time.
let s = String::from("hello");
let t = s; // ownership moves from s to t
println!("{s}"); // ERROR: borrow of moved value: `s`
Assigning s to t moves the string.
There's now one owner, t, and s is dead.
Reach for s again and the compiler refuses, naming the exact line.
In a language with shared mutable pointers this would be a silent bug (two variables aliasing one buffer, one of them freeing it first), and Rust turns it into a compile error.
Why move instead of copy?
A String owns a buffer on the heap.
Copying it on every assignment would mean duplicating that buffer over and over, silently.
Rust makes the cheap thing (a move: hand over the pointer) the default and the expensive thing (a deep copy with .clone()) something you ask for out loud.
Small values that live entirely on the stack don't have this problem.
Integers, bool, char, and fixed-size arrays of them implement the Copy trait, so assigning one duplicates the bits instead of moving:
let a = 5;
let b = a; // a is copied, not moved
println!("{a} {b}"); // both fine
The rule of thumb: a type that owns nothing on the heap and is cheap to duplicate is Copy, and the move rules never bite it.
Everything else moves.
This is the first chapter where moves matter, because String is the first heap-owning type you've met.
Borrowing, using a value without taking it, is the next chapter.
Here we just get comfortable with ownership changing hands.
When a function parameter has an owned type like String (no & in front), calling the function moves the argument in.
The caller's binding is no longer usable afterwards.
The value lives at the callee now, and will be dropped when the callee finishes (unless it hands ownership back via the return value, which is exactly what happens here).
Note the signature: String in, String out.
Implement the body by mutating the parameter (s.push_str(...)) and then returning s.
Because you own s, you're free to mutate it directly: ownership implies the right to modify.
Useful from the standard library
String::push_strappends a&strto an ownedString. No allocation if there's spare capacity.- The parameter needs
mut s: Stringto callpush_stron it. Mutability is a property of the binding, not the type, so even an owned value has to be declaredmutbefore you can mutate it. Themutis local to the function and doesn't appear in the type.
/// Takes ownership of a String and modifies it.
/// When you pass a String to this function, ownership transfers.
fn take_ownership(s: String) -> String {
let mut s = s;
s.push_str(" - owned by Rust!");
s
}
// Alternative:
//
// fn take_ownership(s: String) -> String {
// format!("{s} - owned by Rust!")
// }
//
// This reuses `s` only to read it, and builds a brand-new String for the
// result. Under the hood `format!` expands to roughly:
//
// let mut buf = String::new();
// buf.write_fmt(format_args!("{s} - owned by Rust!")).unwrap();
// buf
//
// So instead of growing the moved-in buffer in place (the `push_str`
// version), it allocates a fresh buffer and writes both pieces into it.
// The original `s` is dropped at the end of the function either way.
#[test]
fn test_take_ownership() {
let s = String::from("Rust");
let result = take_ownership(s);
// Note: s is no longer valid here! It was moved.
assert_eq!(result, "Rust - owned by Rust!");
}
take_ownership moved a String.
This step shows the other half of the rule: a Copy type is duplicated instead, so the caller keeps its value.
double takes an i32 by value.
Because i32 is Copy, the caller's variable is still alive after the call.
The body is one expression; the lesson is in the test, where x is read again after being passed in.
Useful from the standard library
i32, the other integer types,bool,char, and fixed-size arrays ofCopyvalues all implementCopy. Assigning or passing one duplicates its bits instead of moving it.- Heap-owning types like
StringandVec<T>are deliberately notCopy. When you need a second owner of one of those, ask for it withClone::clone.
/// Doubles a number.
///
/// `i32` is a `Copy` type, so the caller's value is duplicated bit-for-bit
/// when it's passed in. The original stays usable after the call (see the
/// test), which is exactly what does *not* happen with a `String`.
fn double(n: i32) -> i32 {
n * 2
}
#[test]
fn test_double() {
let x = 21;
let y = double(x);
assert_eq!(y, 42);
// x is still usable here: i32 is Copy, so `double(x)` copied it
// instead of moving it.
assert_eq!(x, 21);
}
You moved a String into a function and back out, and saw that an i32 copies instead of moving.
What we learned
- Assigning or passing a non-
Copyvalue moves it. The old binding is dead, and using it again is a compile error, not a runtime surprise.- A move hands over ownership cheaply (just the pointer). Rust makes deep copies explicit through
.clone().Copytypes (integers,bool,char, fixed-size arrays of those) duplicate bit-for-bit instead of moving, so the original stays usable.- The owner is responsible for the value: when it goes out of scope, the value is dropped, with no garbage collector involved.
- Borrowing, coming up next, lets you hand a value to a function without giving up ownership at all.