Andrew Mercer
on this page

Understanding Ownership, Moves, and Borrowing

When you write a program, you work with values: numbers, text, lists, and more complicated data. A program needs to keep track of where that data lives, who is allowed to use it, and when it can be cleaned up.

Different programming languages handle these questions in different ways. Rust has a system called ownership that makes many of these rules explicit and checks them while your code is being compiled.

Let's build up the idea from the beginning.

The big idea: who owns a value?

Imagine you lend a friend a book.

There is a difference between giving the book away and letting your friend borrow it:

  • If you give it away, it belongs to your friend now.
  • If you lend it, you still own it; your friend just gets to use it for a while.

Programs have a similar distinction. Sometimes code transfers ownership of data. Other times, it only needs temporary access to that data.

Rust gives these ideas specific names: moving and borrowing.

Moving: transferring ownership

A move transfers ownership of a value from one variable or part of a program to another.

For example:

let first = String::from("hello");
let second = first;

println!("{second}"); // Works
// println!("{first}"); // Error: `first` no longer owns the String

A String owns the memory used to store its text. In this example, ownership moves from first to second. Afterward, second is the owner, so Rust won't let the program use first as if it still owned that same string.

This isn't Rust arbitrarily making things difficult. It is enforcing a clear rule about who owns the data.

You can think of it like changing the name on a library book's checkout record. The book hasn't been duplicated; responsibility for it has moved to someone else.

This behavior is called move semantics.

Borrowing: access without ownership

Sometimes a function or a variable only needs to inspect a value. It doesn't need to become the owner.

That's what borrowing is for.

In Rust, & creates a reference:

let name = String::from("Ada");
let borrowed_name = &name;

println!("{borrowed_name}");
println!("{name}"); // Still works

borrowed_name is a reference to the string, not a second owned String. The original variable still owns the data.

The distinction is:

  • Move: ownership changes hands.
  • Borrow: code gets access to a value while ownership stays where it is.

Rust uses references to make borrowing explicit.

Two kinds of borrowing

Rust has two main kinds of references:

  • &T is an immutable reference. You can read the value through it.
  • &mut T is a mutable reference. You can modify the value through it, subject to Rust's borrowing rules.

For example:

let mut score = 10;

let score_ref = &mut score;
*score_ref += 1;

println!("{score}"); // 11

The * here accesses the value through the reference. You don't need to understand every detail of that syntax immediately; the important idea is that the reference allows a temporary, controlled way to change the original value.

Rust restricts how references can be used together. In broad terms, code can have multiple immutable references to a value, or one mutable reference at a time, with additional rules about how those references overlap. These restrictions help prevent conflicting changes and unsafe access.

Copying: when the original stays usable

Some values are small and straightforward to duplicate. Rust calls this behavior Copy.

let first = 10;
let second = first;

println!("{first}");  // Works
println!("{second}"); // Works too

Both variables remain usable because integers implement Copy.

Types such as String do not implement Copy. Copying a string's owned data involves more than duplicating a small fixed-size value, so ordinary assignment moves it instead.

A type can sometimes support explicit duplication through the Clone trait:

let first = String::from("hello");
let second = first.clone();

println!("{first}");  // Works
println!("{second}"); // Works too

clone() asks the type to create a separate value. Depending on the type, that may involve copying data and can have a cost. It's useful when you genuinely need another owned value, but it shouldn't be the automatic response to every ownership problem.

What is a partial move?

A value can contain other values. For example, a struct might contain a name, a version, and some settings.

If code moves one non-Copy field out of a larger value, that is called a partial move. The remaining fields may still exist, but the original whole value can no longer necessarily be used as though it were untouched.

This is one reason Rust pays attention to how values are unpacked in patterns, such as in match expressions. A pattern can bind fields by value, which may move them, or bind them by reference, which lets code inspect them without taking ownership.

When you only need to examine a value, borrowing is often the right tool.

Lifetimes: how long is a reference valid?

A reference is useful only while the value it points to is still valid. Rust's lifetime rules help ensure that references don't outlive the data they refer to.

For example, Rust prevents a reference from escaping a scope if the value it points to is going away at the end of that scope.

You will sometimes see lifetime annotations in Rust, such as 'a. These annotations describe relationships between references and how long they need to remain valid. They do not normally make data live longer; they help express constraints the compiler must check.

You don't need to learn every lifetime detail at once. First get comfortable with ownership, moves, and borrowing. Lifetimes make more sense once references feel familiar.

Why does Rust have these rules?

Ownership and borrowing help Rust catch problems before a program runs, including:

  • Trying to use a value after ownership has moved away.
  • Accidentally treating the same owned resource as if it had multiple independent owners.
  • Keeping a reference to data that is no longer valid.
  • Making conflicting changes through references.

Other languages make different trade-offs. Some rely heavily on garbage collection, which automatically reclaims objects that are no longer in use. Rust uses ownership rules and compile-time checks to provide memory safety without requiring a garbage collector.

No approach removes every possible bug, but Rust's model catches a useful class of memory and reference errors early.

A practical way to think about it

When working with a value, ask yourself:

  1. Who owns this value?
  2. Do I need to take ownership, or do I only need to inspect it?
  3. If I need to change it, should I use a mutable reference?
  4. Will this value or reference still be valid where I want to use it?
  5. Do I truly need a separate owned copy, or would borrowing be enough?

These questions are often more useful than memorizing a compiler message or applying a suggested fix without understanding it.

Quick reference

Concept Plain-language meaning
Ownership Which part of the program is responsible for a value.
Move Transfer ownership to another variable or place.
Move semantics The language behavior that defines how ownership transfers.
Borrowing Temporarily access a value without taking ownership.
Reference A way to access a value through borrowing.
Immutable reference (&T) A reference that allows reading.
Mutable reference (&mut T) A reference that allows controlled modification.
Copy A trait for values that can be duplicated implicitly while leaving the original usable.
Clone A trait for explicitly creating a duplicate value.
Partial move Moving one part out of a larger value.
Lifetime Rules describing how long references must remain valid.

The takeaway

Ownership is the larger concept. Moving, borrowing, copying, and lifetimes are related pieces of Rust's approach to managing data safely.

At first, these rules can feel unfamiliar. With practice, they become a way to reason about your program: who owns each value, who can access it, and how long that access is valid.