Question
I need to find the index of an element in a Rust vector of strings. Here is my current code:
fn main() {
let test: Vec<String> = vec![
"one".to_string(),
"two".to_string(),
"three".to_string(),
"four".to_string(),
];
let index: i32 = test
.iter()
.enumerate()
.find(|&r| r.1.to_string() == "two".to_string())
.unwrap()
.0;
}
This produces the following error:
error[E0308]: mismatched types
--> src/main.rs:9:22
|
9 | let index: i32 = test
| ______________________^
10 | | .iter()
11 | | .enumerate()
12 | | .find(|&r| r.1.to_string() == "two".to_string())
13 | | .unwrap()
14 | | .0;
| |__________^ expected i32, found usize
I assume this happens because enumerate() returns a tuple like (usize, _). If that is correct, how should I convert usize to i32 here? Also, is there a better way to find the index of a matching element in a vector, array, or slice?
Short Answer
By the end of this page, you will understand how to find the index of an element in a Rust Vec, array, or slice, why Rust returns usize for indexes, and when to use position() instead of enumerate().find(). You will also see safer ways to handle missing values and avoid unnecessary string allocations.
Concept
In Rust, indexes for arrays, slices, and vectors use the type usize. That is because usize is the natural integer type for memory indexing on the current platform.
If you want to find the index of an element, Rust usually gives you two common patterns:
iter().position(...)when you only need the indexiter().enumerate().find(...)when you need both the index and the value during the search
For your case, position() is the simpler and more idiomatic choice.
A key detail is that searching may fail. Because of that, position() returns an Option<usize>:
Some(index)if a match is foundNoneif no match is found
This matters because Rust wants you to handle the possibility that the value is not present.
Another important point is string comparison. If you already have a String in the vector, you do not need to call .to_string() on each element just to compare it with a string literal. Rust can compare &String and &str directly in many common cases.
So instead of building new strings repeatedly, you can write:
let index = test.iter().position(|s| s == "two");
This is shorter, clearer, and more efficient.
Why this matters in real programming:
- Searching collections is extremely common.
- Choosing the right iterator method makes code easier to read.
- Using
usizecorrectly avoids type mismatches. - Avoiding unnecessary allocations improves performance.
Mental Model
Think of a vector like a row of numbered boxes.
- The value is what is inside each box.
- The index is the box number.
Rust uses usize for the box number because that type is designed for counting and indexing memory.
Now imagine you want to find the box containing the label "two".
position()means: "Walk through the boxes and tell me the number of the first one that matches."enumerate()means: "As I walk through the boxes, also tell me each box number."find()means: "Stop when you find the first matching item."
If you only want the box number, position() is like asking directly for the answer. Using enumerate().find() is like asking for extra information you may not actually need.
Syntax and Examples
The most idiomatic way to find an index in Rust is usually position().
fn main() {
let test = vec![
"one".to_string(),
"two".to_string(),
"three".to_string(),
"four".to_string(),
];
let index = test.iter().position(|s| s == "two");
println!("{:?}", index); // Some(1)
}
index has type Option<usize>.
If you want the raw index and are sure it exists
fn main() {
let test = vec!["one", "two", "three", "four"];
let index = test.iter().position(|s| *s == "two").unwrap();
println!("{}", index); // 1
}
Use unwrap() only when you are certain the value exists. Otherwise, handle the None case safely.
Safe handling with match
fn main() {
let test = vec!["one", "two", "three", "four"];
match test.iter().position(|s| *s == "five") {
Some(index) => println!("Found at index {}", index),
None => println!("Not found"),
}
}
Using enumerate() and find()
This is valid, but more verbose when you only need the index:
fn main() {
let test = vec!["one", "two", "three", "four"];
let result = test
.iter()
.enumerate()
.find(|(_, value)| **value == "two");
println!("{:?}", result); // Some((1, "two"))
}
Converting usize to i32
If you truly need an i32, convert explicitly:
fn main() {
let test = vec!["one", "two", "three", "four"];
let index_usize = test.iter().position(|s| *s == "two").unwrap();
let index_i32 = i32::try_from(index_usize).unwrap();
println!("{}", index_i32);
}
Using i32::try_from(...) is safer than as i32 because it checks whether the value fits.
Step by Step Execution
Consider this example:
fn main() {
let items = vec!["one", "two", "three"];
let result = items.iter().position(|item| *item == "two");
println!("{:?}", result);
}
Step by step:
itemsis created as a vector with three string slices.items.iter()creates an iterator over references to each element.- First item:
&"one" - Second item:
&"two" - Third item:
&"three"
- First item:
position(|item| *item == "two")checks each item in order.- Check index
0:"one" == "two"→false - Check index
1:"two" == "two"→true
- Check index
- As soon as a match is found,
position()stops. - It returns
Some(1). println!("{:?}", result)printsSome(1).
If the item were not found, the result would be None.
Now compare that with enumerate():
fn main() {
let items = vec!["one", "two", "three"];
let result = items
.iter()
.enumerate()
.find(|(index, item)| {
println!("checking index {} with value {}", index, item);
**item == "two"
});
println!("{:?}", result);
}
This gives each element together with its index, producing pairs like:
(0, &"one")(1, &"two")(2, &"three")
If a match is found, the result becomes Some((1, &"two")).
Real World Use Cases
Finding an index is useful in many real programs:
- Form processing: find the position of a field name in a list of columns.
- Configuration parsing: locate a specific key or argument in a sequence.
- Command-line tools: find where a flag appears in
args. - Data cleanup: find the first invalid value in a record list.
- Game logic: locate a player, card, or item in a collection.
- UI state: find the selected tab or active menu item.
Example: finding a command-line flag
fn main() {
let args = vec!["app", "--verbose", "--config", "settings.toml"];
if let Some(index) = args.iter().position(|arg| *arg == "--config") {
println!("Config flag found at index {}", index);
}
}
Example: locating a column in CSV-style data
fn main() {
let headers = vec!["id", "name", "email"];
let email_col = headers.iter().position(|h| *h == "email");
println!("{:?}", email_col); // Some(2)
}
Real Codebase Usage
In real Rust codebases, developers often use position() together with safe control flow.
Pattern: validate input before using the index
fn find_user_column(headers: &[&str]) -> Result<usize, String> {
headers
.iter()
.position(|h| *h == "user_id")
.ok_or_else(|| "Missing 'user_id' column".to_string())
}
This converts Option<usize> into Result<usize, String> for better error reporting.
Pattern: guard clause
fn remove_flag(args: &[&str]) {
let Some(index) = args.iter().position(|arg| *arg == "--debug") else {
println!("Debug flag not present");
return;
};
println!("Debug flag at index {}", index);
}
This avoids nested match blocks.
Pattern: search without allocating
fn contains_name(names: &[String], target: &str) -> Option<usize> {
names.iter().position(|name| name == target)
}
Notice that target is &str, not String. This avoids creating a new owned string just for comparison.
Pattern: use enumerate() when you need both index and value
fn print_invalid_items(items: &[i32]) {
if let Some((index, value)) = items.iter().enumerate().find(|(_, v)| **v < 0) {
println!("First invalid value {} at index {}", value, index);
}
}
This is a good case for enumerate().find() because the code wants both pieces of information.
Common Mistakes
1. Using i32 for indexes
Broken code:
let index: i32 = vec.iter().position(|x| *x == 10).unwrap();
Why it fails:
- Rust collection indexes are
usize, noti32.
Fix:
let index: usize = vec.iter().position(|x| *x == 10).unwrap();
Or convert safely if another API requires i32:
let index: i32 = i32::try_from(vec.iter().position(|x| *x == 10).unwrap()).unwrap();
2. Calling .to_string() unnecessarily
Broken style:
.find(|(_, s)| s.to_string() == "two".to_string())
Why it is a problem:
- It creates new
Stringvalues unnecessarily. - It makes code slower and harder to read.
Better:
.position(|s| s == "two")
3. Using unwrap() when the value may not exist
Broken code:
let index = items.iter().position(|x| *x == "missing").unwrap();
Why it is dangerous:
unwrap()will panic if the item is not found.
Better:
if let Some(index) = items.iter().position(|x| *x == "missing") {
println!("Found at {}", index);
} else {
println!("Not found");
}
4. Using enumerate().find() when position() is enough
Less clear:
let index = items.iter().enumerate().find(|(_, x)| **x == 5).unwrap().0;
Clearer:
let index = items.iter().position(|x| **x == 5).unwrap();
5. Using as for unchecked integer conversion
Risky code:
let index_i32 = index_usize as i32;
Why to avoid it:
- On large values, it may silently truncate or wrap depending on context.
Better:
let index_i32 = i32::try_from(index_usize).unwrap();
Comparisons
| Approach | Returns | Best when | Notes |
|---|---|---|---|
iter().position(predicate) | Option<usize> | You only need the index | Most idiomatic for index lookup |
iter().enumerate().find(predicate) | Option<(usize, &T)> | You need both index and value | More verbose |
contains(value) | bool | You only care whether it exists | Does not give the index |
binary_search(value) | Result<usize, usize> | Data is sorted | Faster on sorted collections |
position() vs enumerate().find()
let index = items.iter().position(|x| *x == "two");
- Simple
- Direct
- Returns only the index
let result = items.iter().enumerate().find(|(_, x)| **x == "two");
- Returns both index and value
- Useful when you need both
- Slightly harder to read
usize vs i32
| Type | Typical use | Can index collections? |
|---|---|---|
usize | Sizes, lengths, indexes | Yes |
i32 | General signed integer math | No, not directly |
In Rust, prefer usize for indexes unless another API specifically requires a different integer type.
Cheat Sheet
Find index
let index = items.iter().position(|x| *x == target);
- Return type:
Option<usize> Some(i)if foundNoneif not found
Get index when item must exist
let index = items.iter().position(|x| *x == target).unwrap();
Safe handling
match items.iter().position(|x| *x == target) {
Some(i) => println!("Found at {}", i),
None => println!("Not found"),
}
Need both index and value
let result = items.iter().enumerate().find(|(_, x)| **x == target);
Convert usize to i32
let i = i32::try_from(index_usize).unwrap();
Avoid this
s.to_string() == "two".to_string()
Use this instead:
s == "two"
Rules to remember
- Collection indexes in Rust are
usize position()is usually the best tool for index lookup- Searching can fail, so expect
Option - Avoid unnecessary allocations during comparison
- Use
try_fromfor safe integer conversion
FAQ
Why does Rust use usize for indexes?
Because usize is the platform-sized unsigned integer designed for indexing memory and collection positions.
What is the easiest way to find an index in a Rust vector?
Use iter().position(...) when you only need the index.
let index = vec.iter().position(|x| *x == 42);
Should I convert a Rust index to i32?
Only if another function or API specifically requires i32. Otherwise, keep indexes as usize.
Is enumerate().find() wrong?
No. It is valid and useful when you need both the index and the matching value. If you only need the index, position() is usually better.
What happens if the item is not found?
position() returns None. If you call unwrap() on that, your program will panic.
Can I search a slice or array the same way?
Yes. Arrays, slices, and vectors all support iteration, so iter().position(...) works for all of them.
Is comparing String with &str allowed in Rust?
Yes, in common cases Rust supports comparing them directly, so you usually do not need .to_string() just for comparison.
Mini Project
Description
Build a small Rust program that searches a list of usernames and reports the position of a requested name. This demonstrates how to use position() on a vector, how to handle Option<usize>, and how to avoid unnecessary string allocations during comparison.
Goal
Create a program that finds a username in a list and prints either its index or a friendly not-found message.
Requirements
- Create a vector containing at least four usernames
- Search for a target username using an iterator-based approach
- Print the index if the username exists
- Print a clear message if the username does not exist
- Keep the index as
usizeunless conversion is explicitly needed
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.