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 (num: , p: &<>)
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: <> = [];
= ;
(num, &primes) {
primes.(num);
}
}
(num: , p: &[]) {
&i p {
num > i && num % i == {
;
}
}
}
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.
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
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
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 |
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);
(& primes);
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 is owned, the loop can consume it. If is borrowed, each item is yielded as a reference.
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
Accessing Cargo Package Metadata in Rust
Learn how to read Cargo package metadata like version, name, and authors in Rust using compile-time environment macros.
Associated Types vs Generic Type Parameters in Rust: When to Use Each
Learn when to use associated types vs generic parameters in Rust traits, with clear rules, examples, and practical API design advice.
Can a Struct Extend Another Struct in Rust? Composition vs Inheritance
Learn how Rust handles struct reuse without inheritance, using composition, traits, and wrapper structs with practical examples.