Right, so you’ve got a collection of things, and each of those things might be a success (Ok) or a failure (Err). You’ve been a good Rustacean and modeled this with a Vec<Result<T, E>>. But now you want to work with a Vec<T>—you want all the good stuff, and if any of them failed, you want to bail out with that first error you find. This is such a common desire that the standard library has a one-liner for it. Behold, collect().

No, really. It’s not just for turning an iterator into a Vec. It’s way more powerful than that. It can perform a “type-level transformation,” which is a fancy way of saying it can change the shape of your collection based what you ask for. In this case, you’re asking it to transform a collection of Results into a Result of a collection.

let results: Vec<Result<u32, &str>> = vec![Ok(1), Ok(2), Ok(3)];
let collected: Result<Vec<u32>, &str> = results.into_iter().collect();
assert_eq!(collected, Ok(vec![1, 2, 3]));

let results_with_error: Vec<Result<u32, &str>> = vec![Ok(1), Err("Oh no!"), Ok(3)];
let collected_with_error: Result<Vec<u32>, &str> = results_with_error.into_iter().collect();
assert_eq!(collected_with_error, Err("Oh no!"));

See? Magic. But it’s the good kind of magic—the kind with a straightforward implementation you could have written yourself. collect leverages the FromIterator trait. Result<V, E> implements FromIterator<Result<T, E>> for this exact purpose. Its internal logic is essentially this: start with an empty Vec, then iterate, pushing each Ok value. The moment it hits an Err, it stops iterating and returns that error. It’s a classic short-circuit.

The Devil’s in the Details: Ownership and Iterators

Notice I used into_iter() in the example above, which consumes the Vec and gives us ownership of each Result. This is the most common and ergonomic way to do it. But what if you need to keep your original Vec<Result<T, E>> around? You can’t use into_iter() because that takes ownership. You’d use iter() instead, but now you have a bunch of &Result<T, E> references, not the Results themselves.

This is where a little cloned() or copied() action comes in handy, provided your T and E are clonable or copyable.

let results: Vec<Result<u32, &str>> = vec![Ok(1), Ok(2), Ok(3)];

// We want to collect but keep `results` for later use
let collected: Result<Vec<u32>, &str> = results.iter().copied().collect();
//                                                      ^^^^^^
// Transforms &Result<u32, &str> to Result<u32, &str> via Copy

assert_eq!(collected, Ok(vec![1, 2, 3]));
// We still have `results`!
println!("{:?}", results); // Prints [Ok(1), Ok(2), Ok(3)]

If your error type is a String or something more complex, you might need a map to clone the error inside the Result before collecting, but honestly, if you’re in that situation, you should probably just use into_iter() and be done with it.

When You Want All The Errors, Not Just The First One

Here’s the rub with the standard collect: it fails fast. It gives you the first error it finds and discards any successful results it had already collected. Sometimes that’s exactly what you want. But other times, you’re doing validation on a dataset and you want to know every single thing that’s wrong, not just the first one the compiler happened to stumble upon.

The standard library’s collect for Result is useless here. You need a different strategy. This is where the brilliant partition method on iterators shines. It lets you split your iterator into two collections based on a predicate. Combine it with a bit of logic, and you’ve got a solution.

fn validate_all(data: &[u32]) -> Result<Vec<u32>, Vec<String>> {
    let (oks, errs): (Vec<_>, Vec<_>) = data
        .iter()
        .map(|&num| {
            if num % 2 == 0 {
                Ok(num)
            } else {
                Err(format!("{} is not even", num))
            }
        })
        .partition(Result::is_ok);

    // Now we have Vec<Result<u32, String>> for both.
    // We need to unpack the Ok values and Err values.
    let successes: Vec<u32> = oks.into_iter().map(Result::unwrap).collect();
    let failures: Vec<String> = errs.into_iter().map(Result::unwrap_err).collect();

    if failures.is_empty() {
        Ok(successes)
    } else {
        Err(failures)
    }
}

let data = vec![2, 4, 5, 6, 7];
match validate_all(&data) {
    Ok(all_evens) => println!("All good: {:?}", all_evens),
    Err(errors) => println!("Failed with: {:?}", errors), // Prints: ["5 is not even", "7 is not even"]
}

Is it more verbose? Yes. But it’s also incredibly clear and gives you maximum control. You get to decide what the final Ok and Err types are. This pattern is your go-to when “all or nothing” isn’t good enough.