When I was a kid my parents moved a lot, but I always found them. — Rodney Dangerfield
Most languages let you keep using a variable after you've assigned it somewhere
else. For types such as String, Rust rejects this with a compile error.
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 you can
no longer use s. With manual memory management, keeping two pointers to the
same buffer can cause a use-after-free if one frees the buffer while the other
still uses it.
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 the default: move the String without copying its
heap buffer. To duplicate the string's buffer, call .clone() explicitly.
Types that can safely be duplicated bit-for-bit can implement Copy. 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
Integers and bool are Copy, while heap-owning values such as String move
by default. A type's documentation tells you whether it implements Copy when
the distinction is not obvious.
String makes moves visible because it owns a heap allocation. These exercises
trace how ownership changes hands. Borrowing lets a function use a value while
its caller keeps ownership.
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 now belongs to the called function. It will be
dropped when that function finishes unless the function returns it to the caller,
as it does here.
Read the signature as String in, String out. The function can choose to
mutate the value it owns, but the parameter binding still needs mut before you
can change it. Append the text to s, then return the same String so the
caller owns it again.
Useful from the Standard Library
String::push_strappends a&strto an ownedString. It doesn't allocate 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.
// Only the `format!` version drops the original `s` when the function ends.
// The `push_str` version returns it, moving ownership back to the caller.
// Appending may reallocate its buffer, but returning it does not drop it.
#[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. With a Copy type, you get a copy 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. Look at the test: it
reads x again after passing it to double.
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.
/// 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).
/// Passing a `String` by value would move it instead.
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 no longer holds the value; using it again before reassigning it is a compile error.- Moving a
Stringtransfers its pointer, length, and capacity without copying the heap buffer. To copy the buffer too, call.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 lets a function use a value without taking ownership from its caller.
Extra practice to explore at your own pace. These chapters do not count toward course progress.