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)
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
primesis created asvec![2, 3, 5].numis set to7.vector_is_prime(num, &primes)is called.&primescreates a shared reference to the vector.- Ownership stays in
main. - The function can read the vector.
- Ownership stays in
- Inside the function, the loop checks
2,3, and5.7 % 2 != 07 % 3 != 07 % 5 != 0
- No divisor is found, so the function returns
true. - Back in
main,primes.push(num)runs.- This works because
mainstill ownsprimes.
- This works because
primesbecomes[2, 3, 5, 7].- 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.
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
Resultorbool - 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
| Approach | Function parameter | Ownership change? | Can caller still use vector? | Typical use |
|---|---|---|---|---|
| Move | Vec<u64> | Yes | No | Function should take over the data |
| Borrow vector | &Vec<u64> | No | Yes | Read-only access to a vector |
| Borrow slice | &[u64] | No | Yes | Read-only access to any contiguous sequence |
| Mutable borrow | &mut Vec<u64> | No ownership transfer | Yes, after borrow ends | Function needs to modify the vector |
| Clone | Vec<u64> with primes.clone() | Function owns clone | Yes | Rarely needed; can be expensive |
&Vec<T> vs &[T]
| Type | When to use | Why |
|---|---|---|
&Vec<T> | When you specifically need vector methods | Ties the function to Vec |
&[T] | When you only need to read elements | More flexible and idiomatic |
If your function only loops through items, &[T] is usually the better choice.
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 notCopy.- 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.
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.
Keep learning
Related questions
Absolute Values in Rust: Using the abs() Method
Learn how to calculate absolute values in modern Rust with abs(), handle signed integer overflow, and choose safer alternatives.
Accepting Iterators in Rust Functions with impl Iterator
Learn how to write Rust functions that accept HashMap iterators using generic bounds or impl Iterator, avoiding trait object and lifetime errors.
Access Enum Variant Values in Rust with Pattern Matching
Learn how to extract and print values stored in Rust enum variants using match and if let pattern matching, with practical examples.