How to use Iterator position and find

Use position to get the index and find to get the value of the first item matching a condition in a Rust iterator.

When you need the index versus the value

You are processing a list of user actions. You need to find the first action that matches a specific type. In Python, you might reach for list.index() or a generator expression. In JavaScript, findIndex or find. Rust gives you two methods on the Iterator trait that map to these needs, but they return different types and enforce different guarantees. position returns the index. find returns a reference to the value. The choice dictates how you handle the result, how you manage borrows, and whether you can mutate the collection afterward.

The core distinction

position answers "where is it?" It returns Option<usize>. The usize is the count of items the iterator yielded before finding a match. find answers "what is it?" It returns Option<&T>. The reference points directly to the matching element.

Both methods take a closure that returns a boolean. Both are lazy consumers. They iterate through the source, calling your closure on each item, and stop immediately when the closure returns true. If the iterator exhausts without a match, both return None.

Think of a librarian. position is the librarian telling you "Aisle 4, Shelf 2". You get the location and can go retrieve the book yourself, or mark the shelf for later. find is the librarian handing you the book directly. You have the book in your hands, but you don't necessarily know which shelf it came from.

Minimal example

fn main() {
    let scores = vec![85, 92, 78, 95, 88];

    // position returns Option<usize>.
    // The closure receives &i32 from iter().
    // Pattern match &s to destructure to i32.
    let first_nine_idx = scores.iter().position(|&s| s >= 90);

    // find returns Option<&i32>.
    // The closure receives &&i32 because find takes a reference to the item.
    // Pattern match &&s to destructure to i32.
    let first_nine_val = scores.iter().find(|&&s| s >= 90);

    // Handle the Option results.
    if let Some(idx) = first_nine_idx {
        println!("Found at index: {}", idx);
    }

    if let Some(val) = first_nine_val {
        println!("Found value: {}", val);
    }
}

The closure syntax looks different between the two. position calls your closure with the item itself. find calls your closure with a reference to the item. This is a convention of the trait definition. position takes FnMut(&T) -> bool. find takes FnMut(&T) -> bool as well, but because find returns a reference, the closure signature in the trait is actually FnMut(&T) -> bool where T is the item type. Wait, let's check the types carefully. Iterator::position takes FnMut(Self::Item) -> bool. Iterator::find takes FnMut(&Self::Item) -> bool. This difference forces the closure pattern matching to shift. position passes the item directly. find passes a reference to the item.

Trust the borrow checker on the closure arguments. If the types don't line up, the compiler will reject you with E0308 (mismatched types). Pattern matching in the closure arguments is the standard way to resolve this without writing *x or **x everywhere.

How the iterator pipeline works

Iterators in Rust are lazy. Calling .iter() on a vector does not loop. It returns an iterator object that knows how to produce items one by one. position and find are terminal operations. They consume the iterator. They call next() repeatedly until the closure returns true or the iterator returns None.

When you call position, the iterator yields items. The closure evaluates each item. If the closure returns true, position captures the current count and returns Some(count). The iteration stops. Remaining items are never visited. This short-circuiting is efficient. You don't pay the cost of scanning the entire collection if the match is early.

find works the same way, but it returns a reference to the item inside the collection. The lifetime of that reference is tied to the iterator source. If you iterate over a vector, the reference lives as long as the vector. If you iterate over a temporary, the reference dies when the temporary drops. The compiler enforces this with lifetime elision rules. You rarely write lifetimes explicitly here, but the compiler tracks them.

Realistic scenario: Updating game state

Imagine a game with a list of entities. You need to find the first enemy that is within attack range. If you find one, you want to deal damage to it.

Using find gives you a reference to the enemy. You can read its health. You cannot mutate it through an immutable reference. If you need to mutate, you must use find_mut.

Using position gives you the index. You can use the index to mutate the entity via indexing. This is useful if you need to perform multiple operations or if the mutation logic is complex.

#[derive(Debug)]
struct Entity {
    name: String,
    health: u32,
    distance: u32,
}

fn main() {
    let mut entities = vec![
        Entity { name: "Goblin".to_string(), health: 50, distance: 10 },
        Entity { name: "Orc".to_string(), health: 100, distance: 3 },
        Entity { name: "Dragon".to_string(), health: 500, distance: 20 },
    ];

    // Find the index of the first enemy within range 5.
    // position returns usize, which we can use for mutation.
    let target_idx = entities.iter().position(|e| e.distance <= 5);

    if let Some(idx) = target_idx {
        // Mutate via index.
        // This works because we have the index, not a reference.
        entities[idx].health -= 10;
        println!("Hit {} at index {}", entities[idx].name, idx);
    }

    // Alternatively, use find_mut to get a mutable reference directly.
    // find_mut returns Option<&mut T>.
    if let Some(target) = entities.iter_mut().find(|e| e.distance <= 5) {
        target.health -= 10;
        println!("Hit {} directly", target.name);
    }
}

The position approach separates the search from the mutation. You find the index, then you mutate. This is safe because the index is just a number. It doesn't hold a borrow. You can drop the iterator, do other work, and then use the index to mutate. The find_mut approach holds a mutable borrow during the search and the mutation. You cannot use the collection for anything else while that borrow is active.

Choose the approach that fits your borrow constraints. If you need to mutate immediately and hold the reference, find_mut is cleaner. If you need the index for later or for random access, position is the tool.

Pitfalls and compiler errors

The reference dance in closures

The most common stumbling block is the closure argument type. iter() yields &T. position passes the item to the closure, so the closure receives &T. find passes a reference to the item, so the closure receives &&T.

If you write |x| for find, x is &&T. Comparing x to a value might fail because of type mismatch. The compiler emits E0308. The fix is pattern matching. Write |&x| to destructure the outer reference. Now x is &T. If you need the value and it implements Copy, write |&&x| to get T.

Convention dictates using pattern matching in closure arguments to strip references. It makes the code readable and avoids dereference operators.

Borrow conflicts

If you try to mutate the collection while searching, the borrow checker stops you. You cannot hold a mutable borrow and an immutable borrow at the same time.

let mut data = vec![1, 2, 3];

// This fails.
// data.iter() borrows data immutably.
// data.push borrows data mutably.
// The borrow checker rejects this with E0502.
let idx = data.iter().position(|&x| x > 2);
data.push(4);

The error E0502 says you cannot borrow as mutable because it is also borrowed as immutable. The fix is to separate the search and the mutation. Compute the index first. Then mutate.

let idx = data.iter().position(|&x| x > 2);
// The iterator is dropped here. The immutable borrow ends.
if let Some(i) = idx {
    data[i] = 99; // Mutable borrow is now allowed.
}

Logical index versus memory index

position returns a count, not a memory offset. This matters when you chain iterators or use adapters.

let a = vec![1, 2, 3];
let b = vec![4, 5, 6];

// chain creates a logical sequence.
// position returns the count of items yielded.
let idx = a.iter().chain(b.iter()).position(|&&x| x == 5);
// idx is 4. Items 1, 2, 3, 4 were yielded before 5.
// There is no single collection with index 4.

If you try to use this index to access a or b, you get a panic or wrong data. position on a chain gives you the logical position in the stream. You cannot use it for random access on the underlying collections. Use position on chains only when you need the count or when you are iterating the chain again.

Dangling references with find

find returns a reference. The reference is valid only as long as the source is valid. If you return the reference from a function, the source must outlive the function.

fn find_target() -> Option<&String> {
    let data = vec!["a".to_string(), "b".to_string()];
    // This fails.
    // data is dropped at the end of the function.
    // The reference would dangle.
    // Compiler rejects with E0515.
    data.iter().find(|s| s == "b")
}

The compiler protects you. You cannot return a reference to local data. You must return the value, the index, or ensure the source lives long enough.

Decision matrix

Use find when you need the value immediately and don't care about its location. Use find when you want to read the value and the closure logic is simple. Use find when the iterator source is immutable and you only need inspection.

Use position when you need the index to mutate the collection later. Use position when you need to perform random access or multiple operations on the found element. Use position when working with chained or mapped iterators and you need the logical count of items processed.

Use find_mut when you need to modify the element in place without storing an index. Use find_mut when the mutation is immediate and you can hold the mutable borrow for the duration of the update.

Use enumerate().find() when you need both the index and the value in a single pass. This returns Option<(usize, &T)>. It is slightly more verbose but avoids calling the iterator twice.

Where to go next