Map vs for_each

fn main() {

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

    a.iter_mut().for_each(|x| *x += 1);
    println!("{:?}",a)
}
///////////////////////////////////////
fn main() {
    let mut a = vec![1,2,3];
 

    a.iter_mut().map(|x| *x += 1);
    println!("{:?}",a)
}

What is the exact difference between these besides map returning the element why map doesnt mutate the variable ?
Would be good if i can get clear explanation. (btw im newbie, came from nodejs)

Based this video both methods are does something to each item.(video has been set to where defines has been made)

1 Like

Iterators are lazy - they don't do anything unless they're iterated explicitly. map will do its work when the resulting iterator is advanced via next - either directly, or with for loop, or with some consuming method like collect.

2 Likes

In Node.js map is equivalent to Rust's map(…).collect(). Node.js doesn't have a lazy/non-collecting map like Rust does.

You should have gotten a warning during compilation about the map not being used correctly here. Check out whatever console/log/terminal you use to compile Rust.

2 Likes

Ah i think i understand.
Here is the quick question ,where to use map rather than for_each then ? Can i have scenerios for both please ?

You use map() when you want to transform each element in a sequence. You rarely for_each() used in real Rust code[1], but it lets you consume the sequence of elements.

As an example, say I want to get a list of Package names. One strategy is to take an iterator of Packages and turn it into an iterator of strings which we can lazily print in a for-loop (the for-loop is the one consuming iterator and where the transform closure will be called). Another strategy is to create an empty Vec and use the for_each() to push each name into it.

struct Package {
    name: String,
}

impl Package {
    fn new(name: &str) -> Package {
        Package {
            name: name.to_string(),
        }
    }
}

fn main() {
    let packages: Vec<Package> = vec![
        Package::new("first"),
        Package::new("second"),
        Package::new("third"),
    ];

    println!("Map:");
    let names = packages.iter().map(|pkg| &pkg.name);
    for name in names {
        println!("{name}");
    }

    let mut names = Vec::new();
    packages.iter().for_each(|pkg| names.push(&pkg.name));
    println!("For Each: {names:?}");
}

(playground)

I prefer the map() version because it avoids mutating any collections (i.e. the let mut names = Vec::new() bit).


  1. Probably because Rust's for-loop doesn't have all the "quirks" that JavaScript's various for-in/for-of/for loops do? ↩︎

2 Likes

You should read compiler diagnostics carefully. Your code produces the following warning:

   Compiling playground v0.0.1 (/playground)
warning: unused `Map` that must be used
 --> src/main.rs:3:5
  |
3 |     a.iter_mut().map(|x| *x += 1);
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: `#[warn(unused_must_use)]` on by default
  = note: iterators are lazy and do nothing unless consumed

Which basically explains the exact reason as to why your map example doesn't work. Don't just ignore warnings.


I don't see a good reason to use for_each() other than personal stylistic perference; it's completely equivalent with a for loop over the iterator, except that it's less flexible (because you can e.g. break or return from a loop – you can't do that with the closure in for_each()).

You would use map any time you want to transform an iterator over one type to an iterator over another type. Your usage of trying to mutate inside map is highly non-idiomatic, don't do that.

5 Likes

One reason is that it works by internal iteration. Some iterators like Chain and FlatMap can avoid extra branches when they don't have to re-enter next for every single item in a for loop.

8 Likes

That's right, but I still find it slightly bemusing since it makes people wonder "why didn't the programmer just write a loop".

2 Likes

A complete sidenote: there's a proposal to add lazy iterators in ecmascript, with some experimental APIs in Node.js in the stream module. I've been thinking about that proposal about once a week for some time now.

1 Like

One nice use of for_each is as a first step to https://docs.rs/rayon/latest/rayon/iter/trait.ParallelIterator.html#method.for_each.

But yes, for any loop body doing material work, one should generally use a normal for loop, not Iterator::for_each. It's there for μoptimization cases and for the occasional times where .for_each(do_the_thing) is tidier after the end of a long chain of things.

4 Likes

Why dont u just use this ? and What transform means exactly? I have understand how map works thanks to @Cerber-Ursi but still couldnt get where to use ?

   // let names = packages.iter().map(|pkg| &pkg.name);
    let names = packages.iter();
    for name in names {
        println!("{}",name.name);
    }

When I say "transform" I mean "create a new value based on the previous value".

For example, a Redux reducer will transform your app's state from one its previous state to the next state.

Sure you could. That was just a contrived example where I was using map() to convert from an iterator of Packages to an iterator of strings by executing |pkg| &pkg.name on each package.

I'd deliberately split up the bit doing the transformation work (.map(...) in my example, name.name access in yours) from the bit that uses the package names (your for-loop and println!()).

Here is a more realistic example where I iterate over all the keys in a particular resources dictionary, convert them from &str to Text, then for each one I try to look up the "resource value" that corresponds to the key (which may fail), transforming the Text to a Result<(Text, Vector), Error>. Finally, we take this stream of items and collect() it into a Result<OrdMap<Text, Vector<u8>>, crate::Error>.

fn resource_values(
    db: &dyn Frontend,
) -> Result<OrdMap<Text, Vector<u8>>, crate::Error> {
    let doc = db.parse()?;

    doc.resources
        .keys()
        .map(Text::from)
        .map(|name| {
            let value = db.resource_value(name.clone())?;
            Ok((name, value))
        })
        .collect()
}

It also makes it easier to see what is happening ("first we turn the keys into Text, then we look up their value") because each operation is in its own map() call instead of all being shoved into the same for-loop body. Chaining on another operation also becomes trivial - just add another map() before we collect the stream of objects into the OrdMap.

2 Likes

Thanks sir, that helped me to understand better.

Another example: to calculate 1*1 + 2*2 + … + n*n, I would use something akin to

(1..=n).map(|k| k * k).sum()

(Aside from the fact that there is a closed-form formula)

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.