Skip to content

Commit c33bd8a

Browse files
Rollup merge of #126100 - scottmcm:decaveat-map, r=Mark-Simulacrum
Reword the caveats on `array::map` Thanks to #107634 and some improvements in LLVM (particularly [`dead_on_unwind`](https://llvm.org/docs/LangRef.html#parameter-attributes)), 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 keep encouraging `Iterator::map` where an array wasn't actually ever needed.
2 parents 120b188 + bdc08ae commit c33bd8a

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
@@ -516,20 +516,47 @@ impl<T, const N: usize> [T; N] {
516516
///
517517
/// # Note on performance and stack usage
518518
///
519-
/// Unfortunately, usages of this method are currently not always optimized
520-
/// as well as they could be. This mainly concerns large arrays, as mapping
521-
/// over small arrays seem to be optimized just fine. Also note that in
522-
/// debug mode (i.e. without any optimizations), this method can use a lot
523-
/// of stack space (a few times the size of the array or more).
524-
///
525-
/// Therefore, in performance-critical code, try to avoid using this method
526-
/// on large arrays or check the emitted code. Also try to avoid chained
527-
/// maps (e.g. `arr.map(...).map(...)`).
528-
///
529-
/// In many cases, you can instead use [`Iterator::map`] by calling `.iter()`
530-
/// or `.into_iter()` on your array. `[T; N]::map` is only necessary if you
531-
/// really need a new array of the same size as the result. Rust's lazy
532-
/// iterators tend to get optimized very well.
519+
/// Note that this method is *eager*. It evaluates `f` all `N` times before
520+
/// returning the new array.
521+
///
522+
/// That means that `arr.map(f).map(g)` is, in general, *not* equivalent to
523+
/// `array.map(|x| g(f(x)))`, as the former calls `f` 4 times then `g` 4 times,
524+
/// whereas the latter interleaves the calls (`fgfgfgfg`).
525+
///
526+
/// A consequence of this is that it can have fairly-high stack usage, especially
527+
/// in debug mode or for long arrays. The backend may be able to optimize it
528+
/// away, but especially for complicated mappings it might not be able to.
529+
///
530+
/// If you're doing a one-step `map` and really want an array as the result,
531+
/// then absolutely use this method. Its implementation uses a bunch of tricks
532+
/// to help the optimizer handle it well. Particularly for simple arrays,
533+
/// like `[u8; 3]` or `[f32; 4]`, there's nothing to be concerned about.
534+
///
535+
/// However, if you don't actually need an *array* of the results specifically,
536+
/// just to process them, then you likely want [`Iterator::map`] instead.
537+
///
538+
/// For example, rather than doing an array-to-array map of all the elements
539+
/// in the array up-front and only iterating after that completes,
540+
///
541+
/// ```
542+
/// # let my_array = [1, 2, 3];
543+
/// # let f = |x: i32| x + 1;
544+
/// for x in my_array.map(f) {
545+
/// // ...
546+
/// }
547+
/// ```
548+
///
549+
/// It's often better to use an iterator along the lines of
550+
///
551+
/// ```
552+
/// # let my_array = [1, 2, 3];
553+
/// # let f = |x: i32| x + 1;
554+
/// for x in my_array.into_iter().map(f) {
555+
/// // ...
556+
/// }
557+
/// ```
558+
///
559+
/// as that's more likely to avoid large temporaries.
533560
///
534561
///
535562
/// # Examples

0 commit comments

Comments
 (0)