Question

How to Fix a "Use of Moved Value" Error in Rust with Borrowing

Question

I am trying to solve Project Euler problem 7 in Rust by computing the 10,001st prime number. As part of the solution, I wrote a function that checks whether a number is prime by looking through a vector of previously found primes.

Here is the code:

fn main() {
    let mut count: u32 = 1;
    let mut num: u64 = 1;
    let mut primes: Vec<u64> = Vec::new();
    primes.push(2);

    while count < 10001 {
        num += 2;
        if vector_is_prime(num, primes) {
            count += 1;
            primes.push(num);
        }
    }
}

fn vector_is_prime(num: u64, p: Vec<u64>) -> bool {
    for i in p {
        if num > i && num % i != 0 {
            return false;
        }
    }

    true
}

The compiler reports this error:

error[E0382]: use of moved value: `primes`
 --> src/main.rs:9:31
  |
9 |         if vector_is_prime(num, primes) {
  |                               ^^^^^^ value moved here, in previous iteration of loop
  |
  = note: move occurs because `primes` has type `std::vec::Vec<u64>`, which does not implement the `Copy` trait

What do I need to do to primes so that I can use it inside vector_is_prime without losing access to it in the loop?

Short Answer

By the end of this page, you will understand why Rust says a value was "moved," why Vec<T> is not copied automatically, and how to fix the problem by borrowing with references instead of transferring ownership. You will also see how iteration behaves differently for owned values and borrowed values.

Concept

In Rust, every value has an owner. When you pass a value like Vec<u64> into a function by value, ownership is transferred to that function. This transfer is called a move.

That is what happens here:

if vector_is_prime(num, primes) {

Your function is declared like this:

fn vector_is_prime(num: u64, p: Vec<u64>) -> bool

Because p is a Vec<u64>, the function takes ownership of the vector. After that call, main no longer owns primes, so it cannot use it again in the next loop iteration or call primes.push(num).

This matters because Rust uses ownership to guarantee memory safety without a garbage collector. Instead of allowing multiple uncontrolled owners, Rust makes ownership explicit.

When you want a function to read data without taking it away from the caller, you should pass a reference:

fn vector_is_prime(num: u64, p: &Vec<u64>) -> bool

Now the function borrows the vector instead of owning it.

In many cases, Rust programmers prefer an even more flexible type:

fn vector_is_prime(num: u64, p: &[u64]) -> bool

This takes a slice, which can borrow a whole vector or part of one. It is usually more idiomatic when a function only needs read-only access to a sequence.

There is also a second issue in your primality logic. This condition:

if num > i && num % i != 0 {
    return false;
}

is backwards for prime checking. A number is not prime if it is divisible by a smaller prime:

if num > i && num % i == 0 {
    return false;
}

So the core concept here is:

  • Vec<T> passed by value moves ownership
  • &Vec<T> or &[T] borrows instead
  • Borrowing lets you inspect data without losing it
  • Iteration over owned and borrowed collections behaves differently

Mental Model

Think of a Vec<u64> as a box of files in an office.

  • Move: you hand the whole box to someone else. You no longer have it.
  • Borrow: you let someone look through the box at your desk. They can read it, but you still own it.
  • Mutable borrow: you let someone edit the files temporarily, but only one editor at a time.

In your code, vector_is_prime(num, primes) is like handing over the whole box. After that, main cannot keep using it.

What you really want is: "Please inspect these files, but give me nothing back because I still own them." That is a shared reference:

vector_is_prime(num, &primes)
Take Quiz

Syntax and Examples

The fix is to borrow the vector instead of moving it.

Borrow a vector with &Vec<T>

fn main() {
    let mut primes: Vec<u64> = vec![2];
    let num = 7;

    if vector_is_prime(num, &primes) {
        primes.push(num);
    }
}

fn vector_is_prime(num: u64, p: &Vec<u64>) -> bool {
    for &i in p {
        if num > i && num % i == 0 {
            return false;
        }
    }
    true
}

More idiomatic: use a slice &[T]

fn main() {
    let mut primes: Vec<u64> = vec![2];
    let num = 7;

    if vector_is_prime(num, &primes) {
        primes.push(num);
    }
}

fn vector_is_prime(num: u64, p: &[u64]) -> bool {
    for &i in p {
        if num > i && num % i == 0 {
            return false;
        }
    }
    true
}

Why for &i in p?

If p is borrowed, iterating over it yields references like &u64, not plain u64 values. Writing for &i in p destructures each &u64 into a copied u64.

You could also write:

for i in p {
    if num > *i && num % *i == 0 {
        return false;
    }
}

Both are valid.

Corrected version of your program

fn main() {
    let mut count: u32 = 1;
    let mut num: u64 = 1;
    let mut primes: Vec<u64> = vec![2];

    while count < 10001 {
        num += 2;
        if vector_is_prime(num, &primes) {
            count += 1;
            primes.push(num);
        }
    }

    println!("{}", primes.last().unwrap());
}

fn vector_is_prime(num: u64, p: &[u64]) -> bool {
    for &i in p {
        if num > i && num % i == 0 {
            return false;
        }
    }

    true
}

This compiles because primes is only borrowed during the function call, then used again afterward.

Step by Step Execution

Consider this small example:

fn main() {
    let mut primes = vec![2, 3, 5];
    let num = 7;

    if vector_is_prime(num, &primes) {
        primes.push(num);
    }

    println!("{:?}", primes);
}

fn vector_is_prime(num: u64, p: &[u64]) -> bool {
    for &i in p {
        if num > i && num % i == 0 {
            return false;
        }
    }
    true
}

Step by step

  1. primes is created as vec![2, 3, 5].
  2. num is set to 7.
  3. vector_is_prime(num, &primes) is called.
  4. &primes creates a shared reference to the vector.
    • Ownership stays in main.
    • The function can read the vector.
  5. Inside the function, the loop checks 2, 3, and 5.
    • 7 % 2 != 0
    • 7 % 3 != 0
    • 7 % 5 != 0
  6. No divisor is found, so the function returns true.
  7. Back in main, primes.push(num) runs.
    • This works because main still owns primes.
  8. primes becomes [2, 3, 5, 7].
  9. The program prints the updated vector.

What would happen with a move?

If the function took p: Vec<u64>, then step 3 would transfer ownership into the function. After the call, main could not use primes anymore. That is exactly what the compiler is preventing.

Real World Use Cases

Borrowing instead of moving is extremely common in Rust.

Reading configuration

A function may inspect a configuration struct without taking ownership:

fn validate_config(config: &Config) -> bool

Processing request data

A web handler may read headers or JSON fields by reference.

Working with collections

You often pass &Vec<T> or &[T] when:

  • searching
  • validating
  • filtering
  • summarizing
  • calculating statistics

Logging and formatting

A function may read values to print them, but should not consume them.

Reusing expensive data

Vectors, strings, and structs can be large. Borrowing avoids unnecessary copies and keeps code efficient.

Take Quiz

Real Codebase Usage

In real Rust codebases, developers usually choose parameter types based on intent.

Use ownership when the function should take control

fn save_and_consume(data: Vec<String>)

Use this when the function should own the data and the caller should not use it afterward.

Use shared borrowing for read-only access

fn contains_prime(primes: &[u64], n: u64) -> bool

This is the most common choice for validation, searching, and computation.

Use mutable borrowing when the function should modify existing data

fn add_prime(primes: &mut Vec<u64>, n: u64)

Common patterns

  • Validation: borrow inputs and return Result or bool
  • Guard clauses: return early when invalid conditions are found
  • Helpers: pass slices to utility functions instead of entire vectors by value
  • Error handling: borrow data for checks before mutating it
  • API design: prefer &[T] over &Vec<T> when only sequential read access is needed

For your case, prime checking is a read-only helper, so borrowing is the right pattern.

Common Mistakes

1. Passing a vector by value when you only need to read it

Broken code:

fn check(values: Vec<i32>) -> bool {
    true
}

This consumes values.

Better:

fn check(values: &[i32]) -> bool {
    true
}

2. Iterating in a way that moves the collection

If you own a vector and write:

for x in values {
    println!("{}", x);
}

that loop consumes values.

If you want to keep using it:

for x in &values {
    println!("{}", x);
}

3. Forgetting that borrowed iteration yields references

Broken code:

fn print_all(values: &[u64]) {
    for x in values {
        if x % 2 == 0 {
            println!("{}", x);
        }
    }
}

Here x is &u64, so % does not work directly.

Fix:

fn print_all(values: &[u64]) {
    for &x in values {
        if x % 2 == 0 {
            println!("{}", x);
        }
    }
}

4. Cloning to silence the compiler unnecessarily

You might be tempted to do this:

if vector_is_prime(num, primes.clone()) {

This works, but it copies the whole vector every loop iteration, which is inefficient.

Prefer borrowing:

if vector_is_prime(num, &primes) {

5. Mixing up move errors with lifetime problems

A move error means ownership was transferred. A lifetime error means a reference may not stay valid long enough. They are related to borrowing, but they are not the same thing.

6. Incorrect prime logic

Broken code:

if num > i && num % i != 0 {
    return false;
}

This returns false when a number is not divisible, which is the opposite of what you want.

Correct version:

if num > i && num % i == 0 {
    return false;
}

Comparisons

ApproachFunction parameterOwnership change?Can caller still use vector?Typical use
MoveVec<u64>YesNoFunction should take over the data
Borrow vector&Vec<u64>NoYesRead-only access to a vector
Borrow slice&[u64]NoYesRead-only access to any contiguous sequence
Mutable borrow&mut Vec<u64>No ownership transferYes, after borrow endsFunction needs to modify the vector
CloneVec<u64> with primes.clone()Function owns cloneYesRarely needed; can be expensive

&Vec<T> vs &[T]

TypeWhen to useWhy
&Vec<T>When you specifically need vector methodsTies the function to Vec
&[T]When you only need to read elementsMore flexible and idiomatic

If your function only loops through items, &[T] is usually the better choice.

Take Quiz

Cheat Sheet

Ownership and borrowing quick reference

  • Passing Vec<T> to a function moves it.
  • Passing &Vec<T> borrows it.
  • Passing &[T] borrows it as a slice.
  • Vec<T> is not Copy.
  • Use clone() only when you truly need a separate owned copy.

Common signatures

fn takes_ownership(v: Vec<u64>)
fn borrows_vector(v: &Vec<u64>)
fn borrows_slice(v: &[u64])
fn mutably_borrows(v: &mut Vec<u64>)

Function call examples

takes_ownership(primes);
borrows_vector(&primes);
borrows_slice(&primes);
mutably_borrows(&mut primes);

Iteration rules

for x in primes { }        // moves the vector if you own it
for x in &primes { }       // borrows items as &T
for &x in &primes { }      // copies item values if T is Copy
for x in &mut primes { }   // mutable references to items

Best choice for read-only helper functions

fn helper(values: &[u64]) -> bool

Prime-check condition

if num > i && num % i == 0 {
    return false;
}

Avoid

helper(primes.clone())

unless you really need an owned copy.

FAQ

Why does Rust move Vec into the function?

Because Vec<T> does not implement Copy. Passing it by value transfers ownership to the function.

How do I let a function read a vector without taking it?

Pass a reference:

fn f(v: &[u64])

and call it with:

f(&primes)

Should I use &Vec<T> or &[T] in Rust?

Use &[T] when the function only needs to read elements. It is more flexible and idiomatic.

Why not just use clone()?

You can, but it duplicates the entire vector. That is usually slower and unnecessary when borrowing solves the problem.

What is the difference between move and borrow in Rust?

A move transfers ownership. A borrow gives temporary access without transferring ownership.

Why does for i in p behave differently for owned and borrowed values?

If p is owned, the loop can consume it. If p is borrowed, each item is yielded as a reference.

Can I still modify primes after borrowing it?

Yes, as long as the borrow has ended. In your code, the shared borrow ends after the function call, so primes.push(num) is allowed.

What is the most idiomatic fix for this code?

Change the function to accept a slice:

fn vector_is_prime(num: u64, p: &[u64]) -> bool

and call it with &primes.

Take Quiz

Mini Project

Description

Build a small Rust program that keeps a list of discovered prime numbers and checks whether new numbers are prime by borrowing the existing list. This project demonstrates the difference between owning a vector and borrowing it, which is one of the most important parts of writing Rust functions safely.

Goal

Create a program that finds the first 20 prime numbers using a helper function that borrows a slice of previously found primes.

Requirements

  • Store discovered prime numbers in a Vec<u64>.
  • Write a helper function that takes a borrowed slice of primes.
  • Check only odd numbers after starting with 2.
  • Add each new prime to the vector.
  • Print the final list of primes.
Take Quiz

Keep learning

Related questions

View more