rust-lang / rust-lang/rust-clippy

iter_with_drain false positive when vec capacity should be retained

Open
#8,539 8 comments 7 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

C-bug I-false-positive I-suggestion-causes-error L-nursery
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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.