-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
needless_range_loop intrusiveness #6075
Comments
Could you please elaborate on which cases you find problematic and why? Ideally giving some examples :) |
In this loop: for i in 6 .. L {
if primes[i] && reverse_digits(to!{i, u32}, &mut digs).iter().any(|&d| BAD[to!{d, usize}]) {
primes[i] = false;
}
} Clippy has suggested me to write: for (i, pi) in primes.iter_mut().enumerate().take(L).skip(6) {
if *pi && reverse_digits(to!{i, u32}, &mut digs).iter().any(|&d| BAD[to!{d, usize}]) {
*pi = false;
}
} While this simplifies the code inside the loop slightly (replacing two primes[i] with *pi), the resulting code seems quite more complex than the original one (and I think this change also increases the pressure on the LLVM, because it has to optimize tons of stuff away. Try to look at the asm of rust generated without optimizations. It's hundreds of asm lines for every basic iter_mut+enumerate+take+skip loop). |
Thanks for taking the time to come up with an example. About the increase in compile time, I would say that the problem you point out would be general for iterators, right? Indexing would probably increase run time due to bounds checking, so IMO this seems like a trade-off that depends on each case (which Clippy can't detect/reason about) and should be left for the user to decide. About splitting the lint, I think we could benefit from hearing more opinions on this. Personally, I think the suggestion is fine (modulo false positives of course, and #878 / #398, which seems could be solved by suggesting I will leave the issue opened so that other folks can help to move the discussion forward. |
I don't think the argument against this lint, that it increases compile time is valid. One principle of the Rust Language is exactly this trade-off: sacrifice compile time for safe and efficient code. The efficient code comes from the zero-cost-abstractions Rust provides. Using iterators is one of those zero-cost-abstractions. In addition to that iterators over indices is idiomatic Rust style. If you don't agree with this, I see that this lint isn't for you. But as long as there is no FP, I wouldn't reduce the cases this lint triggers for. |
Related #3032 I agree that the lint is too aggressive. Here is an example I came up with. It may be a little contrived, but I think it illustrates the problem. The loop is not primarily a loop over the let feelings = vec!["alive", "tired", "very tired"];
for miles in 0..3 {
let calories = miles * 100;
let feeling = feelings[miles];
println!("You need {} calories to run {} miles. Then you will feel {}.", calories, miles, feeling);
} |
I would like a version of this clippy where enumerate can be used, but no |
I just put this code into godbolt, to make my point more clear: https://godbolt.org/z/8njPf6 The iterator version optimizes to 177 lines of assembler code, while the index code optimizes to 308 lines of assembler code. As for the performance of both variants on big vectors of the size
So yeah, I don't think there is an argument left for not triggering this lint in the examples given here. #3032 is a good example how this lint could be enhanced with suggesting Clippy should not suggest less optimal code, just because someone likes the (objectively worse) index style better than the idiomatic iterator style. If performance is not of concern for you, feel free to disable this lint or the whole |
I'm not using this clippy for performance, I am using it for (what I view as) clarity. I did't even know how to tell this was a "perf" clippy -- out of interest, how would I find that out? it doesn't seem listed on the terminal, and at https://rust-lang.github.io/rust-clippy/master/index.html#needless_range_loop it seems to be "style"? |
While I haven't benchmarked, I took the snippet of code which was being triggered for me, and in godbolt the "traditional loop" version produces significantly less assembler: |
It produces 9 lines less assembler, yes. But the performance speaks for itself:
To avoid future confusion: We call this a "lint" not a "clippy". Clippy is the tool name providing those "lints". I guess this lint is classified as style, because it is more idiomatic in Rust to use iterators, whenever possible. The classification is guided by what the lint author/reviewer thinks is the most fitting group. I guess we could reclassify this as The documentation states:
So it mentions that this lint also addresses performance, not only style. |
I still believe this lint has grown too large, if we look at the doc page, it says "Checks for looping over the range of 0..len of some collection just to get the values by index", so at least the documentation should be updated to discuss any a..b range. Would a PR which split the check into two pieces, one just handing the case in the documentation (no take or skip required) (which I would count as 'style'), and the current case (counted as 'perf'), be acceptable, in principle? |
I agree the documentation should be updated and improved.
I still don't see the point in that. Then we would have one lint, that suggests optimal code and a second lint, that lints almost the same, but worse. Using iterators everywhere instead of "traditional loops" is idiomatic Rust, and therefore suggesting |
FWIW, there are some cases where replacing Using the clippy recommendation doesn't compile in cases where a mutable borrow is taken within the loop.
|
I have this code fn foo(chunk: u8) {
let mut buf = [' '; 8];
for i in 0..8 {
if chunk & (1 << i) > 0 {
buf[i] = '#';
}
}
print!("{}", String::from_iter(buf))
} and clippy complains about this lint. In this case, I don't think there will be bounds checking because it is clear at compile time that all values in I've tried asking rust-analyzer to rewrite this, and all it does is transform this into |
This is a case where needless_range_loop probably gives a good suggestion:
Clippy says:
But in many many cases similar suggestions are not so good. I suspect that replacing a hundred regular range for loops with iter/iter_mut + enumerates increases the work (the time) the compiler (the back-end) has to do to optimize the code. And in many cases (most cases in my codebase) this suggestion messes up the signficant amount of imperative code inside the loop. So while in general I think such lint is useful to have in Clippy, for me it's one of the most annoying Clippy lints. So I suggest to reduce the number of situations where it fires. and move the rest of situations into a pedentic bin that's disabled by default.
The text was updated successfully, but these errors were encountered: