Start learning free

LESSON 109 OF 414 · ARRAYS I

Vec and Iterators in Rust: map, filter, sum and collect

Lesson Read the full explanation

The everyday list type in Rust is Vec — a growable array that owns its elements. The quickest way to build one is the vec! macro, and because Vec has no plain {} formatting, you print the whole thing with the debug placeholder {:?}:

let nums = vec![10, 20, 30];
println!("{:?}", nums);    // prints [10, 20, 30]

You could process a list with an index loop like for i in 0..nums.len() — it compiles, but it's not how Rust is written. Idiomatic code uses iterators: .iter() borrows each element in turn (the vector stays alive and usable afterwards), and you chain adapters onto it. .map(|x| ...) transforms every element, .filter(|x| ...) keeps only the ones that pass a test, .sum() reduces everything to one number, and .collect() gathers results into a new Vec:

let doubled: Vec<i32> = nums.iter().map(|x| x * 2).collect();

The trap:

.collect() can build many different containers — a Vec, a HashSet, even a String — so the compiler must be told which one you want. Every beginner eventually leaves the annotation off, and compilation stops with this exact error:

error[E0283]: type annotations needed
 --> src/main.rs:3:9
  |
3 |     let squares = nums.iter().map(|x| x * x).collect();
  |         ^^^^^^^                              ------- type must be known at this point
  |
  = note: cannot satisfy `_: FromIterator<i32>`
help: consider giving `squares` an explicit type
  |
3 |     let squares: Vec<_> = nums.iter().map(|x| x * x).collect();
  |                ++++++++

The help: line hands you the fix: annotate the binding, e.g. let squares: Vec<i32> = .... The same rule applies to .sum() — write let sum: i32 = ... so it knows which number type to produce.


Two closure details worth knowing:

Because .iter() only borrows, the closure in filter receives a reference — write *x to dereference it before doing arithmetic like *x % 2 == 0. And when you want the kept values as owned numbers rather than references, add .cloned() before .collect(): it copies each surviving &i32 into a plain i32.


Assignment
Follow the Coding Tutorial to process one list three ways, without a single index variable:

  • Build nums as the vector [1, 2, 3, 4, 5] with the vec! macro.
  • Use .iter().map(...).collect() to build squares, and print it with {:?}.
  • Use .iter().sum() to compute sum, and print it.
  • Use .iter().filter(...).cloned().collect() to build evens, and print it with {:?}.

The exact expected output is:

squares = [1, 4, 9, 16, 25]
sum = 15
evens = [2, 4]

Hint
If the compiler says type annotations needed, give the binding an explicit type: : Vec<i32> for a collect, : i32 for a sum. If it complains inside the filter closure, remember the element arrives as a reference — dereference it with *x.


STEP 1 OF 7
    Ctrl + Enter to check

    RUST · ARRAYS I

    Vec And Iterators in Rust, explained

    The Vec type and how to print it

    Vec is the everyday list type in Rust: a growable array that owns its elements. The quickest way to build one is the vec! macro. A Vec has no plain {} formatting, and println!("{}", nums) fails to compile because the type doesn't implement Display. To print the whole list, use the debug placeholder {:?}:

    let nums = vec![10, 20, 30];
    println!("{:?}", nums);
    [10, 20, 30]

    Iterators instead of index loops

    You can process a vector with for i in 0..nums.len() and index into it, and that compiles. Idiomatic Rust instead uses iterators. Calling .iter() borrows each element in turn, so the vector stays alive and usable afterwards. You then chain adapters onto the iterator:

    • .map(|x| ...) transforms every element;
    • .filter(|x| ...) keeps only the elements for which the closure returns true;
    • .sum() reduces everything to one number;
    • .collect() gathers the results into a new collection, such as a Vec.

    The part between the pipes, such as |x| x * x, is a closure: a small anonymous function whose parameter is named between the bars. Iterators are lazy. map and filter do no work by themselves; the work happens when a final step such as collect or sum pulls the values through. With a closure that prints, the printing happens at the collect call, not where map is written.

    let nums = vec![1, 2, 3, 4, 5];
    let doubled: Vec<i32> = nums.iter().map(|x| x * 2).collect();
    println!("{:?}", doubled);
    [2, 4, 6, 8, 10]

    The type annotation trap

    collect() can build many containers: a Vec, a HashSet, even a String. The compiler has to be told which one you want. If you leave the annotation off, as in let squares = nums.iter().map(|x| x * x).collect();, compilation stops with error[E0283]: type annotations needed. The help text suggests the fix: give the binding a type, such as let squares: Vec<i32> = ....

    The same applies to sum(). Write let sum: i32 = nums.iter().sum(); so that it knows which number type to produce. Another option is the turbofish, collect::<Vec<i32>>(), which puts the type on the call instead of the binding.

    References inside closures

    Because .iter() only borrows, the elements arrive as references. In map, the closure parameter x is a &i32, and arithmetic works directly on references to numbers, so x * x needs no star.

    filter adds a layer: it passes its closure a reference to each item, and the item is already a reference, so x is a &&i32. The operator % is not defined for that type, and x % 2 == 0 fails with cannot calculate the remainder of `&&{integer}` divided by `{integer}`. One * removes a layer, giving a &i32, and that is enough: *x % 2 == 0 compiles.

    The kept elements are still references. A plain collect would produce a Vec<&i32>. Adding .cloned() before collect copies each number out of its reference, so you get a Vec<i32>. For plain numbers, .copied() does the same job.

    The lesson's exercise

    The exercise processes one list three ways, with no index variable.

    1. Build the list. nums is vec![1, 2, 3, 4, 5].
    2. Squares. nums.iter().map(|x| x * x).collect(), with the binding annotated as Vec<i32>. Print it with {:?}.
    3. Sum. nums.iter().sum() with an i32 annotation. Printing a single number needs only {}. Because iter() only borrowed, nums is still usable here.
    4. Evens. filter keeps the numbers where *x % 2 == 0, cloned turns the references into owned numbers, and collect builds the Vec<i32>.
    let nums = vec![1, 2, 3, 4, 5];
    let squares: Vec<i32> = nums.iter().map(|x| x * x).collect();
    println!("squares = {:?}", squares);
    let sum: i32 = nums.iter().sum();
    println!("sum = {}", sum);
    let evens: Vec<i32> = nums.iter().filter(|x| *x % 2 == 0).cloned().collect();
    println!("evens = {:?}", evens);

    The output is:

    squares = [1, 4, 9, 16, 25]
    sum = 15
    evens = [2, 4]

    iter versus into_iter

    The method .iter() borrows, which is why nums survives every chain above. Its sibling .into_iter() consumes the vector and yields owned elements. After nums.into_iter().collect::<Vec<i32>>(), using nums is a compile error: "borrow of moved value". Choose iter() when you still need the vector.

    Key takeaways

    • Print a whole Vec with {:?}, not {}.
    • .iter() borrows the elements; chain map, filter, sum and collect instead of writing index loops.
    • Annotate the result of collect() and sum() so the compiler knows the target type.
    • Elements arrive as references; dereference with * where an operator needs the plain value.
    • Use .cloned() to turn kept references into owned values.
    Keep going with RustAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free