rust-lang / rust-lang/rust-clippy
iter_with_drain false positive when vec capacity should be retained
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 13.5k
- Forks
- 2.2k
- Avg merge
- 2d 10h
- Merged PRs (30d)
- 32
Description
Summary
The iter_with_drain lint is a performance lint that says to replace vec.drain(..) with vec.into_iter(). This is unfortunate because into_iter destroys the capacity of a vector that you might have been planning to reuse, which is the opposite of performance.
Lint Name
iter_with_drain
Reproducer
fn main() {
let mut out = Struct(Vec::new());
let mut pending = Vec::new();
for i in 0..20 {
if i % (pending.len() + 1) == 2 {
out.extend(pending.drain(..));
} else {
pending.push(i);
pending.reverse();
}
}
println!("{:?}", out);
}
#[derive(Debug)]
struct Struct<T>(Vec<T>);
impl<T> Extend<T> for Struct<T> {
fn extend<I: IntoIterator<Item = T>>(&mut self, i: I) {
self.0.extend(i);
}
}
warning: `drain(..)` used on a `Vec`
--> src/main.rs:7:32
|
7 | out.extend(pending.drain(..));
| ^^^^^^^^^ help: try this: `into_iter()`
|
= note: `#[warn(clippy::iter_with_drain)]` on by default
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#iter_with_drain
Following clippy's suggestion makes the code fail to build.
error[E0382]: borrow of moved value: `pending`
--> src/main.rs:6:17
|
3 | let mut pending = Vec::new();
| ----------- move occurs because `pending` has type `Vec<usize>`, which does not implement the `Copy` trait
...
6 | if i % (pending.len() + 1) == 2 {
| ^^^^^^^^^^^^^ value borrowed here after move
7 | out.extend(pending.into_iter());
| ----------- `pending` moved due to this method call, in previous iteration of loop
|
note: this function takes ownership of the receiver `self`, which moves `pending`
--> /home/david/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/iter/traits/collect.rs:267:18
|
267 | fn into_iter(self) -> Self::IntoIter;
| ^^^^
It's possible to get around that by recreating a new vector after into_iter has dropped the old one, but the resulting code is slower than we started with because the old capacity is being destroyed and reallocated in each loop.
- out.extend(pending.drain(..));
+ out.extend(pending.into_iter());
+ pending = Vec::new();
Version
rustc 1.61.0-nightly (285fa7ecd 2022-03-14)
binary: rustc
commit-hash: 285fa7ecd05dcbfdaf2faaf20400f5f92b39b3c6
commit-date: 2022-03-14
host: x86_64-unknown-linux-gnu
release: 1.61.0-nightly
LLVM version: 14.0.0
Additional Labels
@rustbot label +I-suggestion-causes-error
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by locating the implementation of the iter_with_drain lint and run the supplied Rust reproducer. Check how the lint handles a vector that is reused after draining; done means it no longer recommends into_iter() when that suggestion moves the vector or loses capacity needed by later iterations.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- devtools, performance, tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100