Skip to content

Suggest replacing simple loops with specialized folds #2401

@killercup

Description

@killercup

A lot of simple loops can be written using fold-like methods in iterators. Clippy should have a set of lints (maybe even a lint group1) that promotes the more functional style.

Have a look at this code:

let xs = &[1, 2, 3, 4]; // <- ignore that this is a slice for a second

let mut res = vec![];
for x in xs {
    res.push(x);
}

Seasoned functional programmers recognize that is is just a fold/reduce. In Rust, you'd write it like this:

xs.iter().fold(vec![], |mut acc, x| { acc.push(x); acc })

Seasoned Rust programmers would use a specialized fold here (basically a shortcut that makes use of FromIterator):

let res: Vec<_> = xs.iter().collect();
  • clippy should suggest using .collect::<Vec<_>>() for loops that just build vectors

Let's make this a bit more complicated:

let xs = &[1, 2, 3, 4];

let mut res = vec![];
for x in xs {
    if *x > 2 { res.push(x); } 
}

This is still a pretty simple loop, and we can rewrite it like this:

let res: Vec<_> = xs.iter().filter(|&x| *x > 2).collect();

(Sadly, it's not a trivial as it first appeared to be because filter passes a reference to the closure, and we need to rewrite that in the comparison we copied from the if. Instead of |&x| *x > 2 we could also generate |x| **x > 2 or |&&x| x > 2.)

  • clippy should suggest using .filter.collect for these kinds of loops

There are of course a bunch more specialized folders we can recognize. For example:

let mut res = false;
for x in xs {
    if *x > 2 { res = true; } 
}

Isn't it wonderfully procedural? It takes 4 lines and inline mutation to find out if an item in that iterator is greater than two. How about suggesting any instead? (Also see this exisiting lint.)

let res = xs.iter().any(|x| *x > 2);
  • clippy should suggest using any instead of simple loops (that on condition match set a boolean to true and break)
  • clippy should suggest using all instead of simple loops (that on condition match set a boolean to false and break)
  • clippy should suggest using find instead of simple loops (that on condition match set an optional value to Some and break)
  • clippy should suggest using position/rposition instead of simple loops (that count iterations and on condition match store the current iteration number in a variable and break)
  • clippy should suggest using max{,_by,_by_key} instead of simple loops (that compare values and store larger one in variable)
  • clippy should suggest using min{,_by,_by_key} instead of simple loops (that compare values and store smaller one in variable)
  • clippy should suggest using sum/product instead of simple loops
  • ➡️ suggest using contains(y) instead of iter().any(|x| x == y) (Suggest replacing 'iter().any(|x| x == y)' with 'contains' where appropriate #2534)

Haven't found an open issue about that, searched for "loop", "reduce", "fold".

Footnotes

  1. Please name the group after an Office Assistant!

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-lintArea: New lints

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions