Skip to content

Commit 30e79db

Browse files
committed
Reword the caveats on array::map
Thanks to 107634 and some improvements in LLVM (particularly `dead_on_unwind`), the method actually optimizes reasonably well now. So focus the discussion on the fundamental ordering differences where the optimizer might never be able to fix it because of the different behaviour, and encouraging `Iterator::map` where an array wasn't actually ever needed.
1 parent 003a902 commit 30e79db

1 file changed

Lines changed: 41 additions & 14 deletions

File tree

library/core/src/array/mod.rs

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -462,20 +462,47 @@ impl<T, const N: usize> [T; N] {
462462
///
463463
/// # Note on performance and stack usage
464464
///
465-
/// Unfortunately, usages of this method are currently not always optimized
466-
/// as well as they could be. This mainly concerns large arrays, as mapping
467-
/// over small arrays seem to be optimized just fine. Also note that in
468-
/// debug mode (i.e. without any optimizations), this method can use a lot
469-
/// of stack space (a few times the size of the array or more).
470-
///
471-
/// Therefore, in performance-critical code, try to avoid using this method
472-
/// on large arrays or check the emitted code. Also try to avoid chained
473-
/// maps (e.g. `arr.map(...).map(...)`).
474-
///
475-
/// In many cases, you can instead use [`Iterator::map`] by calling `.iter()`
476-
/// or `.into_iter()` on your array. `[T; N]::map` is only necessary if you
477-
/// really need a new array of the same size as the result. Rust's lazy
478-
/// iterators tend to get optimized very well.
465+
/// Note that this method is *eager*. In terms of `Iterator` methods, it's
466+
/// more like `.map(…).collect()`, since it returns a new array.
467+
///
468+
/// That means that `arr.map(f).map(g)` is, in general, *not* equivalent to
469+
/// `array.map(|x| g(f(x)))`, as the former calls `f` 4 times then `g` 4 times,
470+
/// whereas the latter interleaves the calls (`fgfgfgfg`).
471+
///
472+
/// A consequence of this is that it can have fairly-high stack usage, especially
473+
/// in debug mode or for long arrays. The backend may be able to optimize it
474+
/// away, but especially for complicated mappings it might not be able to.
475+
///
476+
/// If you're doing a one-step `map` and really want an array as the result,
477+
/// then absolutely use this method. Its implementation uses a bunch of tricks
478+
/// to help the optimizer handle it well.
479+
///
480+
/// However, in many cases you can instead use [`Iterator::map`] by calling
481+
/// `.iter()` or `.into_iter()` on your array. Rust's lazy iterators tend to
482+
/// get optimized very well.
483+
///
484+
/// For example, rather than doing an array-to-array map of all the elements
485+
/// in the array up-front and only iterating after that completes,
486+
///
487+
/// ```
488+
/// # let my_array = [1, 2, 3];
489+
/// # let f = |x: i32| x + 1;
490+
/// for x in my_array.map(f) {
491+
/// // ...
492+
/// }
493+
/// ```
494+
///
495+
/// It's often better to use a lazy iterator map like this
496+
///
497+
/// ```
498+
/// # let my_array = [1, 2, 3];
499+
/// # let f = |x: i32| x + 1;
500+
/// for x in my_array.into_iter().map(f) {
501+
/// // ...
502+
/// }
503+
/// ```
504+
///
505+
/// so that the elements are mapped-then-processed one at a time instead.
479506
///
480507
///
481508
/// # Examples

0 commit comments

Comments
 (0)