Question

Mapping Over Vectors in Rust with Iterator map

Question

How can I apply a mapping function to every element of a Rust Vec?

This custom function creates a new vector by applying a function to each item:

fn map<A, B>(items: &[A], f: impl Fn(&A) -> B) -> Vec<B> {
    let mut result = Vec::with_capacity(items.len());

    for item in items {
        result.push(f(item));
    }

    result
}

fn add_one(value: &i32) -> i32 {
    *value + 1
}

fn main() {
    let values = vec![1, 2, 3];
    let mapped = map(&values, add_one);

    println!("{:?}", mapped);
}

Why does Vec not provide a built-in map method? Is there an idiomatic standard-library approach that also works with collections such as LinkedList?

Short Answer

Rust maps values through the Iterator trait rather than through a Vec-specific method. You will learn to call iter().map(...) to describe a transformation and collect() to build the resulting collection, usually a Vec.

Concept

map transforms each item in a sequence into a new value. For example, mapping [1, 2, 3] with “add one” produces [2, 3, 4].

In Rust, map is defined by the Iterator trait. A Vec is a collection that can produce an iterator; it is not itself where transformation behavior needs to live.

let values = vec![1, 2, 3];
let incremented: Vec<i32> = values.iter().map(|value| value + 1).collect();

assert_eq!(incremented, vec![2, 3, 4]);

This design matters because the same iterator methods work for many sources of values:

  • Vec<T> and slices
  • LinkedList<T>
  • arrays
  • ranges such as 1..10
  • hash-map keys, values, or entries
  • file lines and other streaming input

map is also lazy. Calling it creates an iterator recipe; it does not immediately create a new vector. A consuming operation such as collect, for, sum, or find makes the iterator do its work.

Vectors are not only for procedural programming. They are general-purpose collections, and Rust supports both explicit for loops and iterator pipelines.

Mental Model

Think of an iterator as a conveyor belt.

  • A Vec is a box containing items.
  • iter() puts the items onto a conveyor belt as references.
  • map() installs a machine that changes each item as it passes.
  • collect() puts the transformed items into a new box.

The transformation machine is not built into every possible box type. Instead, it is attached to the shared conveyor-belt interface: Iterator. That is why both vectors and linked lists can use map.

Take Quiz

Syntax and Examples

The common pattern is:

let output: Vec<OutputType> = input
    .iter()
    .map(|item| /* create an OutputType */)
    .collect();

Map borrowed values

Use .iter() when you want to keep the original vector. The closure receives &T.

fn main() {
    let names = vec!["ada", "grace", "linus"];

    let uppercased: Vec<String> = names
        .iter()
        .map(|name| name.to_uppercase())
        .collect();

    assert_eq!(uppercased, vec!["ADA", "GRACE", "LINUS"]);
    assert_eq!(names[0], "ada");
}

to_uppercase() creates a new String, so the original names vector remains available.

Map owned values

Use .into_iter() when the input vector is no longer needed and the mapping operation needs ownership of each value.

fn main() {
    let names = vec![String::from("Ada"), String::from("Grace")];

    let lengths: Vec<usize> = names
        .into_iter()
        .map(|name| name.len())
        .collect();

    assert_eq!(lengths, vec![3, 5]);
}

After into_iter(), names has been moved and cannot be used.

Use a named function

A closure is convenient, but a function works too when its argument type matches.

fn add_one(value: &i32) -> i32 {
    value + 1
}

fn main() {
    let values = vec![1, 2, 3];
    let mapped: Vec<i32> = values.iter().map(add_one).collect();

    assert_eq!(mapped, vec![2, 3, 4]);
}

Step by Step Execution

Consider this code:

let values = vec![1, 2, 3];
let doubled: Vec<i32> = values.iter().map(|value| value * 2).collect();

Execution proceeds as follows:

  1. values owns the integers [1, 2, 3].
  2. values.iter() creates an iterator that yields &i32: first &1, then &2, then &3.
  3. .map(|value| value * 2) creates a new iterator. It records the transformation but does not yet visit every item.
  4. .collect() asks for all mapped values and constructs the requested result type, Vec<i32>.
  5. The closure runs once for each reference:
    • &1 becomes 2
    • &2 becomes 4
    • &3 becomes 6
  6. doubled becomes vec![2, 4, 6].

Because i32 is Copy, Rust automatically reads its value when evaluating value * 2. For non-Copy values, you may need to clone, borrow, or consume values deliberately.

Real World Use Cases

map is useful whenever every input item should become exactly one output item.

  • API responses: convert database records into public response objects.
  • Display data: convert prices in cents into formatted strings.
  • Parsing: turn input strings into normalized values.
  • Configuration: transform environment-variable strings into application settings.
  • Geometry and graphics: convert points from one coordinate system to another.
  • Data processing: derive a field, such as a list of user IDs, from a list of users.
struct User {
    id: u64,
    name: String,
}

fn main() {
    let users = vec![
        User { id: 10, name: String::from("Ada") },
        User { id: 20, name: String::from("Grace") },
    ];

    let ids: Vec<u64> = users.iter().map(|user| user.id).collect();

    assert_eq!(ids, vec![10, 20]);
}
Take Quiz

Real Codebase Usage

In real Rust projects, iterator chains are often used to express a data flow clearly.

Transform after validation

Use a guard clause for invalid input, then map valid values.

fn normalized_tags(tags: &[String]) -> Vec<String> {
    tags.iter()
        .filter(|tag| !tag.trim().is_empty())
        .map(|tag| tag.trim().to_lowercase())
        .collect()
}

Return errors while transforming

When mapping can fail, collect into a Result.

fn parse_ports(inputs: &[String]) -> Result<Vec<u16>, std::num::ParseIntError> {
    inputs.iter()
        .map(|text| text.parse::<u16>())
        .collect()
}

collect() turns an iterator of Result<u16, _> into Result<Vec<u16>, _>. It returns the first error, or a vector when all parsing succeeds.

Write reusable functions for iterables

If a function only needs to read values, accept a slice when appropriate:

fn square_all(values: &[i32]) -> Vec<i32> {
    values.iter().map(|value| value * value).collect()
}

Using &[i32] lets callers pass a Vec<i32>, an array slice, or another slice without requiring a particular collection type.

Common Mistakes

Expecting map to create a vector immediately

This does not produce a Vec; it produces an iterator:

let values = vec![1, 2, 3];
let mapped = values.iter().map(|value| value + 1);

Fix it by consuming the iterator:

let mapped: Vec<i32> = values.iter().map(|value| value + 1).collect();

Forgetting the result type for collect()

Rust may not know which collection to build.

let result = (1..4).map(|value| value * 2).collect(); // type may be ambiguous

Provide a type annotation or a turbofish:

let result: Vec<i32> = (1..4).map(|value| value * 2).collect();
let also_result = (1..4).map(|value| value * 2).collect::<Vec<i32>>();

Moving a vector accidentally with into_iter()

let names = vec![String::from("Ada")];
let lengths: Vec<usize> = names.into_iter().map(|name| name.len()).collect();
println!("{:?}", names); // error: names was moved

Use .iter() if you need names afterward, or intentionally use .into_iter() when consuming it is correct.

Using map when items should be removed

map always produces one output for each input. To discard items, use filter first:

let numbers = vec![1, 2, 3, 4];
let even_squares: Vec<i32> = numbers
    .iter()
    .filter(|number| *number % 2 == 0)
    .map(|number| number * number)
    .collect();

Writing &Vec<T> when a slice is enough

Prefer &[T] for a read-only function parameter:

fn good(values: &[i32]) -> Vec<i32> {
    values.iter().map(|value| value + 1).collect()
}

A slice is more flexible and still supports iteration.

Comparisons

ApproachBest whenResult behavior
for loop with pushThe loop has multiple statements, mutation, or complex control flowYou explicitly build the result vector
.iter().map(...).collect()Every input maps to one output and the transformation is clearDeclarative, lazy until collected
.iter().filter(...).collect()You only want to keep certain itemsMay produce fewer items
.iter().filter(...).map(...).collect()You need to select items and then transform themFilters first, then transforms survivors
.into_iter().map(...).collect()The source collection can be consumedThe closure receives owned items
.iter_mut().map(...)You need mutable references to elementsUsually paired with for_each; often a for loop is clearer

Vec does not need its own map because its iterator already implements the shared Iterator::map method. LinkedList follows the same approach: call .iter().map(...) and collect into the collection type you want.

use std::collections::LinkedList;

let list: LinkedList<i32> = [1, 2, 3].into_iter().collect();
let mapped: LinkedList<i32> = list.iter().map(|value| value + 1).collect();

assert_eq!(mapped.into_iter().collect::<Vec<_>>(), vec![2, 3, 4]);
Take Quiz

Cheat Sheet

// Borrow elements; keep the original Vec
let output: Vec<B> = input.iter().map(|item: &A| /* B */).collect();

// Consume the Vec; closure receives A
let output: Vec<B> = input.into_iter().map(|item: A| /* B */).collect();

// Transform and keep only selected values
let output: Vec<B> = input
    .iter()
    .filter(|item| /* bool */)
    .map(|item| /* B */)
    .collect();

// A fallible transformation
let output: Result<Vec<B>, E> = input.iter().map(|item| /* Result<B, E> */).collect();
  • map belongs to Iterator, so it works across collection types.
  • map is lazy; use collect, for, sum, count, and similar consumers to run it.
  • Annotate collect() when Rust cannot infer the output collection type.
  • Use .iter() to borrow, .into_iter() to consume, and .iter_mut() to mutably borrow.
  • Use &[T], not &Vec<T>, for functions that only need a read-only sequence.
  • Use filter to remove items; map alone preserves the number of items.

FAQ

Does Rust Vec have a map method?

No. The standard approach is vec.iter().map(...).collect::<Vec<_>>(). map is an Iterator method so it can be shared by many iterable types.

Why is map lazy in Rust?

Laziness lets Rust combine operations such as filter, map, and take without allocating an intermediate collection for each step. Work begins when a consumer such as collect() requests values.

Should I use iter() or into_iter() before map?

Use iter() when you want to keep the original collection and can work with references. Use into_iter() when you no longer need it or need to move each item into the transformation.

How do I map a vector into another Vec type?

Specify the target type:

let lengths: Vec<usize> = words.iter().map(|word| word.len()).collect();

Can I use map on a LinkedList in Rust?

Yes. Call list.iter().map(...) or list.into_iter().map(...), then use collect() to choose the output collection.

When is a for loop better than map?

Use a for loop when each iteration performs several actions, needs break or continue, or mutates state in a way that makes an iterator chain harder to read.

Can map handle errors?

Yes. Return Result from the closure and collect into Result<Vec<T>, E>. Rust will return an error if any mapped operation fails.

Take Quiz

Mini Project

Description

Build a small order-summary transformation. An application often receives raw item names and prices in cents, then needs a display-ready list without changing the original input. This project practices mapping borrowed vector items into a new type and collecting the output.

Goal

Create a function that converts order items into formatted receipt lines and returns them as a Vec<String>.

Requirements

Use a struct to represent an item with a name and a price in cents. Create a function that accepts a slice of items. Use iter, map, and collect rather than manually pushing into a vector. Format each output line as name: $dollars.cents. Keep the original items available after creating the receipt lines.

Take Quiz

Keep learning

Related questions

View more