Skip to content
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

iter_with_drain false positive when vec capacity should be retained #8539

Open
dtolnay opened this issue Mar 15, 2022 · 8 comments
Open

iter_with_drain false positive when vec capacity should be retained #8539

dtolnay opened this issue Mar 15, 2022 · 8 comments
Labels
C-bug Category: Clippy is not doing the correct thing I-false-positive Issue: The lint was triggered on code it shouldn't have I-suggestion-causes-error Issue: The suggestions provided by this Lint cause an ICE/error when applied L-nursery Lint: Currently in the nursery group

Comments

@dtolnay
Copy link
Member

dtolnay commented Mar 15, 2022

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

@dtolnay dtolnay added C-bug Category: Clippy is not doing the correct thing I-false-positive Issue: The lint was triggered on code it shouldn't have labels Mar 15, 2022
@rustbot rustbot added the I-suggestion-causes-error Issue: The suggestions provided by this Lint cause an ICE/error when applied label Mar 15, 2022
dtolnay added a commit to dtolnay/rustversion that referenced this issue Mar 15, 2022
rust-lang/rust-clippy#8539

    error: `drain(..)` used on a `Vec`
      --> src/constfn.rs:51:36
       |
    51 |                 out.extend(pending.drain(..));
       |                                    ^^^^^^^^^ help: try this: `into_iter()`
       |
       = note: `-D clippy::iter-with-drain` implied by `-D clippy::all`
       = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#iter_with_drain
@dtolnay
Copy link
Member Author

dtolnay commented Mar 15, 2022

Mentioning @ldm0 @flip1995 @giraffate since the lint is from #8483.

@ldm0
Copy link
Contributor

ldm0 commented Mar 15, 2022

@dtolnay Could you try std::mem::take(&mut pending).into_iter()?

@ldm0
Copy link
Contributor

ldm0 commented Mar 15, 2022

.into_iter() usually gets better optimization: https://rust.godbolt.org/z/saGc3Tjqr

@ldm0
Copy link
Contributor

ldm0 commented Mar 15, 2022

#8538 This one can also be replaced with mem::take + into_iter. 🤔 I didn't suggest it automatically due to the hardness of ownership checking and otherwise usage checking. So false positive is expected.

@hrxi
Copy link
Contributor

hrxi commented Mar 15, 2022

@dtolnay Could you try std::mem::take(&mut pending).into_iter()?

That also destroys the capacity because it replaces the old vector with an empty one that doesn't have any capacity.

@flip1995
Copy link
Member

I also had concerns about this lint triggering on owned vs referenced vecs, but couldn't come up with a concrete example at the time (I probably should have thought about this harder).

Together with #8538, I think this lint is not ready to be warn-by-default. But I also think this lint still needs some work, so I'll put it in nursery. (next sync is in a little over a week from now, so I won't do a out-of-cycle sync for this)

bors added a commit that referenced this issue Mar 15, 2022
Move iter_with_drain to nursery

cc #8538 #8539

r? `@giraffate`

changelog: Move [`iter_with_drain`] to nursery
@J-ZhengLi J-ZhengLi added the L-nursery Lint: Currently in the nursery group label Apr 25, 2024
@SUPERCILEX
Copy link

I just ran into the same issue:

    let mut results = results.into_vec();
    results.sort_unstable();
    let entries = results
        .drain(..)
        .map_stuff...
        .collect();
    *search_result_buf = results;

I want to reuse the results buffer, so into_iter is wrong.

@ViliamVadocz
Copy link

I wrote another example of the false positive in an older issue: #8538

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-bug Category: Clippy is not doing the correct thing I-false-positive Issue: The lint was triggered on code it shouldn't have I-suggestion-causes-error Issue: The suggestions provided by this Lint cause an ICE/error when applied L-nursery Lint: Currently in the nursery group
Projects
None yet
Development

No branches or pull requests

8 participants