Borrowing and Moving: A Core Programming Concept¶
Imagine you have a toy car.
- Move: You give the car to someone else. You no longer have it.
- Borrow: You let someone look at or use the car, but you still own it and expect to get it back.
Programming languages have different rules about who owns data and when it can be used. Rust makes these rules especially explicit.
1. The general idea: using a value¶
A program stores information in values: numbers, text, objects, records, and so on.
For example, imagine a program has a value called best that contains the best version found so far. The program wants to look at its version number to decide whether a new version is better.
There are two different things the program might do:
- Inspect the value: Read information from it without taking it away.
- Take ownership of the value: Transfer the value somewhere else, so the original owner may no longer use it.
This distinction exists in many programming languages, although each language handles it differently. Some languages freely share references to objects and rely on garbage collection. Others make copying and ownership more explicit. Rust uses ownership and borrowing to make many data-lifetime mistakes visible at compile time.
2. Moving: handing the toy car over¶
A move means ownership of a value transfers to another variable or place.
In Rust, assigning a non-Copy value to another variable usually moves it:
let first = String::from("hello");
let second = first;
// `second` now owns the String.
// `first` can no longer be used.
println!("{second}");
// println!("{first}"); // Compile error: value was moved
Why? A String owns memory for its text. Rust makes sure that memory has one clear owner responsible for it. After the move, second is the owner, so using first as though it still owned the string is not allowed.
Think of changing the name on a library book's checkout record: the book is now checked out to second, not first.
Moves can happen inside patterns, too¶
Moving does not only happen with let second = first. It can also happen when a pattern pulls a value out of another value.
For example, if best is an Option containing a tuple, this pattern can move the tuple's first field into bv:
Some((bv, _))
Here:
Some(...)means theOptioncontains a value.(bv, _)matches a tuple.bvbinds to the tuple's first field._means “I don't need to give the second field a name.”
If that first field is a non-Copy type such as a custom Ver struct, binding it as bv takes ownership of it. That can leave best only partially moved: one part has been moved out, while the rest has not.
A partially moved value generally cannot be used as a complete value anymore.
3. Borrowing: looking without taking¶
A borrow lets code access a value without taking ownership of it.
In Rust, & means “borrow this”:
let name = String::from("Ada");
let borrowed_name = &name;
println!("{borrowed_name}");
println!("{name}"); // Still valid: `name` still owns the String
borrowed_name is a reference to the string, not a second owned String. It lets the program read the value while name remains its owner.
Rust has two common kinds of references:
&T: an immutable borrow. You can read the value through it.&mut T: a mutable borrow. You can change the value through it, subject to Rust's borrowing rules.
A useful beginner rule is: moving transfers ownership; borrowing temporarily grants access.
4. Your compiler error, step by step¶
The original line was:
Some((bv, _)) if bv >= ver => best,
It appears to be part of a match expression. The branch says roughly:
If
bestcontains a tuple, bind its version tobv. Ifbvis greater than or equal tover, returnbest.
The problem is that bv is bound by value. If Ver is not a Copy type, matching this way can move the version out of best. Then the branch tries to return best:
=> best
But best is no longer fully available. Rust reports:
use of partially moved value:
best
In toy-car terms, the code took one part out of the box, then tried to hand over the original complete box. Rust stops you because the box is no longer intact.
Borrow the version instead¶
The compiler suggests:
Some((ref bv, _)) if bv >= ver => best,
In this pattern, ref bv means:
Bind
bvas a reference to the version inside the tuple instead of moving the version out.
So the program can inspect the version and, if the condition matches, still return best.
In modern Rust, you will also commonly see the pattern written by matching on a reference to the whole value:
match &best {
Some((bv, _)) if bv >= &ver => {
// Inspect the existing best value here.
}
_ => {
// Handle the other case here.
}
}
This second example is illustrative; the exact comparison and branch bodies depend on the actual types and logic in your function. The key idea is that &best asks Rust to match by reference, so the match does not take ownership of the contents.
5. What does Copy mean?¶
You may notice the error says Ver does not implement Copy.
Some small types, such as integers and booleans, are Copy:
let a = 10;
let b = a;
println!("{a}"); // Fine
println!("{b}"); // Also fine
For a Copy type, assigning or binding the value copies it, leaving the original usable.
A String is not Copy, because copying it would involve more than simply duplicating a small fixed-size value. Rust would need to decide how to manage the owned text memory. Instead, a normal assignment moves the String.
A custom type such as Ver is not automatically Copy. That is often the right choice, especially if it owns data. Do not add Copy just to silence an error; first understand whether copying that type makes sense.
6. Why Rust cares about ownership¶
Ownership and borrowing help Rust prevent problems such as:
- Using a value after it has been moved.
- Accidentally having multiple paths that try to own and free the same resource.
- Keeping references to data after that data has gone away.
- Uncontrolled mutation through conflicting references.
The compiler checks these rules before the program runs. This can feel strict at first, but the goal is to catch classes of bugs early.
Other languages solve memory and lifetime problems differently. For example, garbage-collected languages commonly track object reachability at runtime. Rust instead combines ownership rules, borrowing, and compile-time checks to give programmers detailed control without a garbage collector.
7. A simple way to debug these errors¶
When Rust reports a move or partial move, ask:
- What value owns the data? In this example, it may be
best. - Which expression might be moving it? Look for assignments, function arguments,
matchpatterns, and destructuring. - Do I really need ownership? If you only need to inspect the data, a reference may be enough.
- Does the value need to be used afterward? If yes, avoid moving a non-
Copypart out of it. - Would cloning be appropriate?
.clone()creates another value when the type supports it, but it can cost time and memory. Use it when a separate owned value is actually needed, not as an automatic fix.
8. Remember this¶
- Move: “You own it now; I can't use my old binding as though I still own it.”
- Borrow (
&): “You can look at it, but I keep ownership.” - Mutable borrow (
&mut): “You can temporarily change it, under Rust's borrowing rules.” - Partial move: “I moved one non-
Copypart out of a larger value, so I can't use the whole value as before.” Copy: “This type can be duplicated implicitly, so the original remains usable.”
When you see an error like use of partially moved value, don't immediately reach for .clone(). First identify what was moved and ask whether the code only needed to borrow it.