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

Warning about needless collect but suggestion does not compile #6420

Open
yvrhdn opened this issue Dec 4, 2020 · 10 comments
Open

Warning about needless collect but suggestion does not compile #6420

yvrhdn opened this issue Dec 4, 2020 · 10 comments
Labels
C-bug Category: Clippy is not doing the correct thing E-hard Call for participation: This a hard problem and requires more experience or effort to work on 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

@yvrhdn
Copy link

yvrhdn commented Dec 4, 2020

Clippy warns about a needless collect, but the suggested code does not compile.

I have something similar to this code (condensed):

let keys = map.keys().copied().collect::<Vec<_>>();

for key in keys.into_iter() {
    // since .keys() has been copied and collected, I'm not
    // borrowing the map anymore and I can mutate the map here.
    map.remove(key);
}

Suggestion from clippy:

warning: avoid using `collect()` when not needed
  --> src/main.rs:18:5
   |
18 | /     let keys = map.keys().copied().collect::<Vec<_>>();
19 | |
20 | |     for key in keys.into_iter() {
   | |_______________^
   |
   = note: `#[warn(clippy::needless_collect)]` on by default
help: Use the original Iterator instead of collecting it and then producing a new one
   |
18 |     
19 | 
20 |     for key in map.keys().copied() {
   |

This suggestion will not compile because maps.keys().copied() borrows the map.

Full code example: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=711425060fb8ac794003067454010a69

Expectation: the warning does not appear because removing the .collect() would break the code.

Instead, this happened: the needless_collect warning triggered. Additionally, it's not possible to mute this warning for a specific line, so I resorted to adding #![allow(clippy::needless_collect)] to the entire file.

Meta

  • cargo +nightly clippy -V: clippy 0.0.212 (1c389ff 2020-11-24) (I use the nightly clippy to get more warnings)
  • rustc -Vv:
rustc 1.48.0 (7eac88abb 2020-11-16)
binary: rustc
commit-hash: 7eac88abb2e57e752f3302f02be5f3ce3d7adfb4
commit-date: 2020-11-16
host: x86_64-apple-darwin
release: 1.48.0
LLVM version: 11.0
@yvrhdn yvrhdn added the C-bug Category: Clippy is not doing the correct thing label Dec 4, 2020
@giraffate
Copy link
Contributor

@rustbot modify labels: +L-suggestion-causes-error

@rustbot rustbot added the I-suggestion-causes-error Issue: The suggestions provided by this Lint cause an ICE/error when applied label Dec 5, 2020
@flip1995
Copy link
Member

flip1995 commented Dec 5, 2020

Couldn't you use the filter method instead? So fileter the map and collect the remainders afterwards. (I just assume here, that in your actual code, you don't remove each element one by one, but only if some condition is met).

@yvrhdn
Copy link
Author

yvrhdn commented Dec 5, 2020

Yeah, that would be better in most cases, but the actual code is more complicated unfortunately.

In my case I want to loop over all keys of the map and for every key delete and add entries into the map until there is only one key left.
Actual code is posted here: https://github.com/kvrhdn/Advent-of-Code/blob/main/advent-of-code-2019/src/day14.rs#L60-L116, this is a solution for the Advent of Code 2019, day 14.

@tigregalis

This comment has been minimized.

@giraffate
Copy link
Contributor

@tigregalis The lint wasn't emitted at nightly rustc 1.50.0-nightly (d32c320d7 2020-12-10). That case seems to be fixed at #6313.

@tigregalis
Copy link

@tigregalis The lint wasn't emitted at nightly rustc 1.50.0-nightly (d32c320d7 2020-12-10). That case seems to be fixed at #6313.

@giraffate So it is. Thanks.

@camsteffen camsteffen added the E-hard Call for participation: This a hard problem and requires more experience or effort to work on label Feb 8, 2021
@dmilith
Copy link

dmilith commented Aug 8, 2021

I've found an even worse case: Code compiles… but the code logic changes: https://twitter.com/dmilith/status/1424370582179692550?s=20

@flip1995
Copy link
Member

flip1995 commented Aug 9, 2021

Oh, so the collect in your case forces the iterator to evaluate. So removing it makes the statement lazy and it won't evaluate. Detecting this is even harder than detecting if the underlying iterator is mutated. Is it time to move this lint to nursery?

@giraffate
Copy link
Contributor

Is it time to move this lint to nursery?

Yes, I think so.

Many false positives have been reported for needless_collect in the indirect usage, but not for the direct usage. So, IMO it would be better that needless_collect is split into two and the indirect usage is downgraded to nursery.

@reivilibre
Copy link

I too have hit a few instances where this lint triggers and the suggestion is not valid because the original collection the iterator was collected from has been moved (e.g. with into_iter() before the result of collect() has been used).

I've obfuscated the identifiers a bit but hopefully this will paint a picture:

let a_b_indices: Vec<Option<usize>> = as
    .iter()
    .map(|a| {
        bs
            .iter()
            .position(|b| b.id == a.id)
    })
    .collect(); // <-- clippy thinks this collect is needless

let b_a_indices: Vec<Option<usize>> = bs
    .iter()
    .map(|b| {
        as
            .iter()
            .position(|a| b.id == a.id)
    })
    .collect(); // <-- clippy thinks this collect is needless

let as_with_contexts: Vec<_> =
    as
        .into_iter() // <-- this moves `as`
        .zip(a_b_indices.into_iter()) // <-- clippy thinks the iterator that was collect()ed could be inlined here
        .enumerate()
        .map(|(a_index, (a, b_index))| {
            let context = (a.id.clone(), a_index, b_index);

            (a, context)
        })
        .collect();

let bs_with_contexts: Vec<_> =
    bs
        .into_iter() // <-- this moves `bs`
        .zip(b_a_indices.into_iter()) // <-- clippy thinks the iterator that was collect()ed could be inlined here
        .enumerate()
        .map(|(b_index, (b, a_index))| {
            let context = (b.id.clone(), a_index, b_index);

            (b, context)
        })
        .collect();

I could probably do it in a nicer way I'm sure, but at least I can't see any way of avoiding the collect (at least not without cloneing as and bs :))

@J-ZhengLi J-ZhengLi added the L-nursery Lint: Currently in the nursery group label Jul 9, 2024
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 E-hard Call for participation: This a hard problem and requires more experience or effort to work on 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

9 participants