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 returnstrue;.sum()reduces everything to one number;.collect()gathers the results into a new collection, such as aVec.
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.
- Build the list.
numsisvec![1, 2, 3, 4, 5]. - Squares.
nums.iter().map(|x| x * x).collect(), with the binding annotated asVec<i32>. Print it with{:?}. - Sum.
nums.iter().sum()with ani32annotation. Printing a single number needs only{}. Becauseiter()only borrowed,numsis still usable here. - Evens.
filterkeeps the numbers where*x % 2 == 0,clonedturns the references into owned numbers, andcollectbuilds theVec<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
Vecwith{:?}, not{}. .iter()borrows the elements; chainmap,filter,sumandcollectinstead of writing index loops.- Annotate the result of
collect()andsum()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.
