Syntax Cache
BlogMethodFeaturesHow It WorksBuild a Game
  1. Home
  2. Rust
  3. Rust Ownership & Borrowing Practice: Move Semantics & Borrow Rules
Rust39 exercises

Rust Ownership & Borrowing Practice: Move Semantics & Borrow Rules

Master Rust ownership rules and borrowing patterns with targeted practice. Learn move semantics, reference types, and the borrow checker.

Common ErrorsQuick ReferencePractice
Warm-up1 / 2

Can you write this from memory?

Declare a String variable `s1` with value "hello", then move it to `s2`.

On this page
  1. 1Compiler Error E0382: Borrow of Moved Value
  2. 2Compiler Error E0502: Cannot Borrow as Mutable
  3. 3Compiler Error E0505: Cannot Move Because It Is Borrowed
  4. 4Compiler Error E0515: Cannot Return Reference to Local Variable
  5. 5Borrows End at Last Use (Non-Lexical Lifetimes)
  6. 6Copy vs Move vs Clone
  7. 7Borrowing Patterns
  8. 8Slices Are Borrows
  9. 9Moving Out of a Struct Field
  10. 10Further Reading
Compiler Error E0382: Borrow of Moved ValueCompiler Error E0502: Cannot Borrow as MutableCompiler Error E0505: Cannot Move Because It Is BorrowedCompiler Error E0515: Cannot Return Reference to Local VariableBorrows End at Last Use (Non-Lexical Lifetimes)Copy vs Move vs CloneBorrowing PatternsSlices Are BorrowsMoving Out of a Struct FieldFurther Reading

Ownership is Rust's most distinctive feature, and the one that trips up most newcomers. The rules are simple: each value has one owner, values are dropped when the owner goes out of scope, and you can borrow values without taking ownership.

Simple in theory. In practice, you're staring at a borrow checker error wondering: Why can't I use this value? Did I move it? Should this be & or &mut?

This page focuses on ownership patterns until the rules become intuition. From move semantics to borrowing to the critical difference between shared and mutable references.

Related Rust Topics
Rust Foundations: Variables, Types & Basic SyntaxRust References & Lifetimes: Lifetime Annotations & Elision

Each value has one owner. Assignment moves ownership (for non-Copy types), making the original invalid. Borrow with & to avoid moving, or call .clone() when you need independent copies.

The most common ownership error. You used a value after ownership was transferred:

// WRONG: using a value after move
let s1 = String::from("hello");
let s2 = s1;
// println!("{s1}"); // error[E0382]: borrow of moved value

// RIGHT Option 1: borrow instead of move
let s1 = String::from("hello");
let s2 = &s1;
println!("{s1}"); // OK: s1 was only borrowed
println!("{s2}");

// RIGHT Option 2: clone if you need independent ownership
let s1 = String::from("hello");
let s2 = s1.clone();
println!("{s1} {s2}"); // both valid

Moves also happen when passing to functions. If you only need to read, take &T instead of T:

// WRONG: takes ownership unnecessarily
// fn print_len(s: String) { println!("{}", s.len()); }

// RIGHT: borrows — caller keeps ownership
fn print_len(s: &str) { println!("{}", s.len()); }

Ready to practice?

Start practicing Rust Ownership & Borrowing: Move Semantics & Borrow Rules with spaced repetition

An active & reference exists while you try to take &mut:

// WRONG: mutable borrow while immutable borrow is active
let mut v = vec![1, 2, 3];
let first = &v[0];
// v.push(4); // error[E0502]: cannot borrow v as mutable
// println!("{first}");

// RIGHT: end the immutable borrow before mutating
let mut v = vec![1, 2, 3];
let first = v[0]; // Copy the value instead of borrowing
v.push(4);        // no conflict, no active borrow
println!("{first}");

The rule: at any point, you can have either one &mut T or any number of &T, never both simultaneously.

Something still holds a reference, so ownership cannot transfer:

// WRONG: moving while borrowed
let s = String::from("hello");
let r = &s;
// let s2 = s;  // error[E0505]: cannot move out of s because it is borrowed
// println!("{r}");

// RIGHT: scope the borrow before moving
let s = String::from("hello");
{
    let r = &s;
    println!("{r}");
}  // r's borrow ends here
let s2 = s;  // move is fine now

A function creates a value and tries to return a reference to it. The value is dropped when the function returns, leaving a dangling pointer:

// WRONG: returning a reference to a local
// fn make_greeting() -> &str {
//     let s = String::from("hello");
//     &s  // error[E0515]: s is dropped here
// }

// RIGHT: return the owned value
fn make_greeting() -> String {
    String::from("hello")
}

If the function creates the data, it must return the owned value. References only work for data that outlives the function call (e.g., passed in by the caller). When references do need explicit lifetime annotations, the references and lifetimes guide covers the syntax.

Borrows end at their last use, not at the closing }. If you get E0502, check whether you can move the last use of the immutable reference above the mutation.

A common source of confusion: borrows don't last until the end of the block. Since Rust 2018, the compiler ends a borrow at its last use, not at the closing }:

let mut v = vec![1, 2, 3];
let first = &v[0];
println!("{first}");  // last use of first — borrow ends here
v.push(4);            // OK! no active borrow

This is called non-lexical lifetimes (NLL). If you get E0502, check whether you can move the last use of the immutable reference above the mutation.

Copy types (integers, bools, floats) are duplicated implicitly on assignment. Move types (String, Vec) transfer ownership. Clone is explicit duplication via .clone()—it may or may not be expensive.

BehaviorTypesWhat happens on assignment
CopyIntegers, bools, char, f32/f64, tuples/arrays of Copy typesImplicit bitwise duplication — original stays valid
MoveString, Vec, Box, any type without CopyOwnership transfers — original is invalid
CloneAny type implementing CloneExplicit duplication via .clone() — may or may not be expensive
let a = 5;
let b = a;         // Copy: a is still valid
println!("{a}");

let s1 = String::from("hi");
let s2 = s1;       // Move: s1 is now invalid
let s3 = s2.clone(); // Clone: s2 is still valid
println!("{s2} {s3}");

Clone calls arbitrary code — for String it allocates a new heap buffer, but for Rc it just increments a counter. "Clone = deep copy" is not always true.

  • Use &T to read without taking ownership.
  • Use &mut T to mutate without taking ownership.
  • Prefer &str over &String — it accepts both String and string literals.
// Prefer &str for read-only string access
fn word_count(s: &str) -> usize {
    s.split_whitespace().count()
}

fn push_excl(s: &mut String) {
    s.push('!');
}

let owned = String::from("hello world");
word_count(&owned);     // &String coerces to &str
word_count("literal");  // &str directly

Slices (&str, &[T]) are references to a contiguous sequence within owned data:

let s = String::from("hello world");
let first_word: &str = &s[..5];    // "hello"
println!("{first_word}");

let nums = [1, 2, 3, 4, 5];
let middle: &[i32] = &nums[1..4];  // [2, 3, 4]
println!("{middle:?}");

Slices borrow the original data, so the same borrow rules apply — you cannot mutate the owned value while a slice is active. The Rust strings guide dives deeper into &str slices specifically.

You cannot move a field out of a borrowed struct. Use std::mem::take or std::mem::replace to swap in a default:

use std::mem;

struct Config {
    name: String,
    retries: u32,
}

let mut config = Config { name: "prod".into(), retries: 3 };
// Take name out, leaving String::default() ("") in its place
let name = mem::take(&mut config.name);
println!("{name}");        // "prod"
println!("{}", config.name); // ""

This is common in builders, state machines, and any pattern where you need to extract an owned value from a mutable reference. For a printable quick-reference of these patterns, see the ownership and borrowing cheat sheet.

  • The Rust Book: Ownership — move semantics, scope, drop
  • The Rust Book: References and Borrowing — & vs &mut rules
  • The Rust Book: The Slice Type — &str and &[T]
  • Rust Blog: Non-Lexical Lifetimes — borrows end at last use

When to Use Rust Ownership & Borrowing: Move Semantics & Borrow Rules

  • Take ownership when you need to store or return a value.
  • Borrow with `&` when you only need to read.
  • Borrow with `&mut` when you need to modify without taking ownership.
  • Use `Clone` when you need a copy and the type supports it.
  • Use `Copy` types (integers, bools, etc.) when you want implicit copying.

Check Your Understanding: Rust Ownership & Borrowing: Move Semantics & Borrow Rules

Prompt

Explain the borrowing rules in Rust.

What a strong answer looks like

At any time, you can have either one mutable reference OR any number of immutable references (not both). References must always be valid, so no dangling pointers. This prevents data races at compile time.

What You'll Practice: Rust Ownership & Borrowing: Move Semantics & Borrow Rules

Understand ownership transfer (move semantics)Know which types are Copy vs MoveCreate shared references with &Create mutable references with &mutApply the borrowing rules (one &mut OR many &)Understand non-lexical lifetimes (borrow ends at last use)Use slices for &str and &[T] borrowsUse mem::take to move out of struct fieldsFix E0382, E0502, E0505, E0515

Common Rust Ownership & Borrowing: Move Semantics & Borrow Rules Pitfalls

  • Symptom: `error[E0382]: borrow of moved value`. Why: You assigned or passed an owned value, then tried to use the original. Fix: Borrow with `&` instead of moving, or call `.clone()` if you need both.
  • Symptom: `error[E0502]: cannot borrow as mutable because it is also borrowed as immutable`. Why: An active `&` reference exists and you tried to take `&mut`. Fix: End the immutable borrow (let the variable go out of scope or stop using it) before mutating.
  • Symptom: `error[E0515]: cannot return reference to local variable`. Why: The local is dropped at the end of the function, so the reference would dangle. Fix: Return the owned value instead of a reference.
  • Symptom: `error[E0505]: cannot move out of value because it is borrowed`. Why: Something still holds a `&` or `&mut` to the value. Fix: Scope the borrow so it ends before the move, or clone.
  • Symptom: Littering `.clone()` everywhere to silence the compiler. Why: Clone works but hides the real issue — your ownership flow is wrong. Fix: Restructure to borrow where possible; clone only when you genuinely need independent ownership.
  • Symptom: Confusion between `let mut x` and `&mut x`. Why: `mut` on a binding means you can reassign it; `&mut` means you have exclusive mutable access to borrowed data. Fix: Treat them as independent concepts — you can have `let mut x = &val` (rebindable shared ref) or `let x = &mut val` (non-rebindable exclusive ref).

Rust Ownership & Borrowing: Move Semantics & Borrow Rules FAQ

Why does assignment move the value?

For types without `Copy`, assignment transfers ownership to prevent double-free bugs. The original variable becomes invalid. This is called a "move".

What types implement Copy?

Simple scalar types: integers, floats, bools, chars. Also tuples and arrays of Copy types. Types that manage heap memory (String, Vec) do not implement Copy, so they must be cloned explicitly.

Can I have multiple mutable references?

No, not to the same data at the same time. You can have one `&mut T` OR any number of `&T`, never both. This prevents data races.

What does "value moved here" mean?

You tried to use a value after ownership was transferred elsewhere (via assignment, function call, etc.). Either borrow instead of moving, or clone the value.

When should I use clone vs borrow?

Borrow when possible since it's zero-cost. Clone when you genuinely need independent ownership. Avoid cloning just to silence the compiler — it usually means the ownership flow needs restructuring.

Don't borrows last until the end of the block?

Not since Rust 2018. Non-lexical lifetimes (NLL) mean a borrow ends at its last use, not at the closing `}`. This is why moving a `println!` above a mutation can fix E0502.

How do I return a reference from a function?

The referenced data must outlive the function. You can return references to data passed in by the caller (with lifetime annotations), but you cannot return a reference to a local variable — return the owned value instead.

Rust Ownership & Borrowing: Move Semantics & Borrow Rules Syntax Quick Reference

Move semantics
let s1 = String::from("hello");
let s2 = s1;
println!("{s2}");
Clone to copy
let s1 = String::from("hello");
let s2 = s1.clone();
println!("{s1} {s2}");
Immutable borrow
let s = String::from("hello");
let len = s.len();
println!("{s} is {len} bytes");
Mutable borrow
let mut s = String::from("hello");
let r = &mut s;
r.push_str(", world");
Function taking ownership
fn takes_ownership(s: String) {
    println!("{s}");
}
Function borrowing
fn word_count(s: &str) -> usize {
    s.split_whitespace().count()
}
Function mutably borrowing
fn change(s: &mut String) {
    s.push_str(", world");
}
Multiple immutable borrows
let s = String::from("hello");
let r1 = &s;
let r2 = &s;
println!("{r1} {r2}");
String slice
let s = String::from("hello");
let slice: &str = &s[0..2];
println!("{slice}");

Rust Ownership & Borrowing: Move Semantics & Borrow Rules Sample Exercises

Example 1Difficulty: 1/5

Fill in the assignment to show that `x` is still usable after the copy to `y`.

x
Example 2Difficulty: 2/5

Fill in the code to create a deep copy of s1 for s2.

s1.clone()
Example 3Difficulty: 2/5

Fill in the function call that moves ownership of `name`.

consume

+ 36 more exercises

Quick Reference
Rust Ownership & Borrowing: Move Semantics & Borrow Rules Cheat Sheet →

Copy-ready syntax examples for quick lookup

Further Reading

  • Rust Newtype Pattern: Catch Unit Bugs at Compile Time18 min read

Start practicing Rust Ownership & Borrowing: Move Semantics & Borrow Rules

Free daily exercises with spaced repetition. No credit card required.

← Back to Rust Syntax Practice
Syntax Cache

Build syntax muscle memory with spaced repetition.

Product

  • Pricing
  • Our Method
  • Daily Practice
  • Design Patterns
  • Interview Prep

Resources

  • Blog
  • Compare
  • Cheat Sheets
  • Vibe Coding
  • Muscle Memory

Languages

  • Python
  • JavaScript
  • TypeScript
  • Rust
  • SQL
  • GDScript

Legal

  • Terms
  • Privacy
  • Contact

© 2026 Syntax Cache

Cancel anytime in 2 clicks. Keep access until the end of your billing period.

No refunds for partial billing periods.