# Should I use map or a for loop?

In general, is there a preferred way to generate a vector when it comes to using either map or a for loop? For example, if i wanted to create a vector of vectors i could do

``````(0..n).map(|idx| (0..n).map(|jdx| some_function(idx,jdx)).collect::<Vec<f32>>()).collect::<Vec<Vec<f32>>>();
``````

or i could

``````let mut result = vec![vec![0.0;n];n];
for idx in 0..n {
for jdx in 0..n {
result[idx][jdx] = some_function(idx,jdx);
}
}
``````

Is there difference in performance or one which is used more commonly?

Thanks

The iterator solution might have fewer bounds checks on the vector, but I think either is fine.

You can eliminate the extra bounds checks in the loop like this:

``````let mut result = vec![vec![0.0;n];n];
for (idx, row) in result.iter_mut().enumerate() {
for (jdx, cell) in row.iter_mut().enumerate() {
*cell = some_function(idx,jdx);
}
}
``````
14 Likes

I think many here would say the 'map' solution is more idiomatic Rust.

Personally I vote for Alice's 'for' loop solution. It says what it does more clearly, and does not have the conceptual overheads of 'map`, lambda's, 'collect` to think about. Or all those noisy type annotations in the way.

2 Likes

alice probably has the best answer (readability/performance), but I tend to prefer functional solutions (e.g. doesn't mutate the *cell) if at all possible. (I have no good reason to - I just do).

BUT, one thing I'd say is the `Vec<Vec<T>>` is less efficient than `Vec<T>` where the width,height (row/col, idx,jdx) are computed externally. I do a lot of image processing, and we rarely perform a double-for loop.. we generally do a single `stride` offset from a cursor (since, if I'm brightening all pixels, I don't CARE what the width/height is; I just want to hit every element in the array).. If I force a 2D structure like this (AND require a double indirection) I'm introducing an unnecessary inefficiency at each row boundary.

From that personal bias, I tend to make all my 2,3,4 D arrays a 1D array; and the above problem becomes moot. You can always have a `fn get(&self,x:usize,y:usize) -> T { self.data[y*self.WIDTH+x] }` call to provide an easy to use API.. Similarly you an easily produce iterators over a single row. BUT you could also make an iterator over a single COLUMN as well (something not efficient to perform with a `Vec<Vec<T>>`).

I think my bias came from doing image processing in Java, where 2D arrays are basically the same as `Vec<Vec<T>>`.. unlike C/C++ where you can make them a single computed value. Happens to suite my rust code just fine.

6 Likes

If `n` is `const`, you can use `std::array::from_fn`:

``````let res: [[f32; N]; N] = std::array::from_fn(|idx|
std::array::from_fn(|jdx| some_function(idx,jdx))
);
``````
4 Likes

That's super nice! Did not know this function existed in `std`!

One interesting thing I found is how `from_fn()` is implemented

``````pub fn from_fn<T, const N: usize, F>(mut cb: F) -> [T; N]
where
F: FnMut(usize) -> T,
{
let mut idx = 0;
[(); N].map(|_| {
let res = cb(idx);
idx += 1;
res
})
}
``````

It turns out you can map an array of one type to an array of another type without `unsafe` code! Neat!

2 Likes

Yes, map on arrays was stabilized in 1.55. Pretty awesome! array - Rust

Well, there's a bunch of `unsafe` underneath it, of course.

If you're curious about implementation details, I've got a PR open drastically changing it to work better: https://github.com/rust-lang/rust/pull/107634

3 Likes

Oh that's really cool!

How drastic are the speed ups? Is there a huge difference in runtime? The asm definitely looks a lot cleaner!

As always with μoptimizations like this, it will depend greatly on the situation. Something with a big body, like `.map(fs::read_to_string)` probably will show no difference at all.

But for something with a trivial body, it can matter. I tossed together this (silly) benchmark:

``````#[bench]
let mut a: [f32; 64] = black_box(core::array::from_fn(|i| i as _));
b.iter(|| {
a = black_box(a.map(|x| x + 3.14));
});
}
``````

And it said 3.3× faster:

``````BEFORE:
test bench_array_map_add_one               ... bench:      57 ns/iter (+/- 0)

AFTER:
test bench_array_map_add_one               ... bench:      17 ns/iter (+/- 0)
``````
2 Likes

Of course, but that is still an amazing speed up! I am awful at statistics, but this does seem like it's significant!

I dont know if your example is just coincidence but if your actual use case is a dynamically sized two dimensional array you will get the biggest performance benefit by using a single allocation instead of nested vectors. You might want to have a look at `ndarray`.

To add to this, a `Vec<Vec<T>>` can have differently sized "rows" whereas with a 1D structure you know everything is as it should be if the length matches `width * height`

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.