Skip to content

Conversation

@bend-n
Copy link
Contributor

@bend-n bend-n commented Oct 7, 2025

lints on

fn squares() -> Vec<(u8, u8)> { (0..8u8) .flat_map(|x| (0..8).map(move |y| (x, y))) .collect::<Vec<_>>() }

changelog: [unnecessary_collect]: create lint

see also #clippy > linting on vector returns where return is collected

name can be bikeshed

@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties label Oct 7, 2025
@rustbot
Copy link
Collaborator

rustbot commented Oct 7, 2025

r? @llogiq

rustbot has assigned @llogiq.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@github-actions
Copy link

github-actions bot commented Oct 7, 2025

Lintcheck changes for 7e91e18

Lint Added Removed Changed
clippy::unnecessary_collect 36 0 0

This comment will be updated if you push new changes

@bend-n bend-n force-pushed the returning_vector_produced_from_from_iterator branch from 1b7f7a8 to a7a20c0 Compare October 7, 2025 13:23
@rustbot
Copy link
Collaborator

rustbot commented Oct 7, 2025

Some changes occurred in clippy_lints/src/doc

cc @notriddle

@bend-n bend-n force-pushed the returning_vector_produced_from_from_iterator branch 3 times, most recently from a8d01c4 to da2cf18 Compare October 7, 2025 13:41
}
})
.collect()
fn collect_doc_replacements(attrs: &[Attribute]) -> impl Iterator<Item = (Span, String)> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why make it more complicated when the caller collects it because it needs a Vec? This should probably a restriction lint.

@@ -1,3 +1,4 @@
#![allow(clippy::unnecessary_collect)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use #[expect] to ignore a lint, and put it as close as possible to the lint emission.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bit difficult, its define_conf!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This convo seems smart I just wanna say smth so I feel like I'm a part of it

Comment on lines 1 to 5
error: this collection may not be necessary. consider returning the iterator itself
--> tests/ui/unnecessary_collect.rs:5:12
|
LL | fn bad() -> Vec<u32> {
| -------- help: perhaps: `impl Iterator<Item = u32>`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest moving the "consider ..." part of the primary message into the help message to replace the "perhaps". IIRC at least rust-analyzer only shows the help message without the code suggestion in the quick fix tab, so just "help: perhaps" would look odd. It also makes the message itself more concise which is always nice

/// ```
#[clippy::version = "1.92.0"]
pub UNNECESSARY_COLLECT,
pedantic,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO this should be a restriction lint, at least as it's currently implemented because it seems too eager to me.

It's certainly useful in a lot of cases, but fundamentally I don't think there is anything wrong with this pattern. I get that it's for beginners, but clippy::pedantic is also enabled by experienced users, and this already introduces quite a few #[allow]s to the codebase.

There's also a bunch of false positives that seem fairly difficult to solve in a good way, like if the iterator has local lifetimes and can't be returned. Or if the method is part of a trait impl where the trait can't be controlled by the user in which case the signature can't be changed. The lint could be made weaker and checks could be added to avoid them, but maybe the goal is to strictly find all instances of this pattern without false negatives even if it means false positives, and you're willing to go through them manually and do some refactoring. If that's the case (which this sounds like to me), restriction sounds more fitting.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ada4a was talking about how it would be good for pedantic..?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not like I'm a team member or anything though, just wanted to share my opinion^^

But yes, some of the linted cases being unsolvable is unfortunate, though pedantic lints are allowed to give more false-positives than the regular ones

@bend-n bend-n force-pushed the returning_vector_produced_from_from_iterator branch from da2cf18 to 98f1059 Compare October 7, 2025 14:04
@rustbot

This comment has been minimized.

@bend-n bend-n force-pushed the returning_vector_produced_from_from_iterator branch from 98f1059 to b359f33 Compare October 8, 2025 10:56
///
/// ### Why is this bad?
///
/// This might not be necessary, and its more idiomatic to simply return the iterator, giving the caller the option to use it as they see fit.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// This might not be necessary, and its more idiomatic to simply return the iterator, giving the caller the option to use it as they see fit.
/// This might not be necessary, and it's more idiomatic to simply return the iterator, giving the caller the option to use it as they see fit.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree that it's more idiomatic to return the iterator. I have certainly seen and written a lot of methods that return Vec via collect. I will gladly admit that omitting the collect may sometimes benefit performance – if the allocation can be removed, which depends on the callers.

Also I would advice avoiding the word "simply". It can come off as condescending. I don't know what is simple for any person reading this, and I shouldn't want to guess.

@bend-n bend-n force-pushed the returning_vector_produced_from_from_iterator branch from 2645d46 to d59599d Compare November 7, 2025 17:56
@rustbot

This comment has been minimized.

@bend-n bend-n requested a review from samueltardieu November 8, 2025 09:23
@bend-n bend-n force-pushed the returning_vector_produced_from_from_iterator branch from c07b6fd to c0d681d Compare November 8, 2025 09:30
@rustbot

This comment has been minimized.

@llogiq
Copy link
Contributor

llogiq commented Nov 14, 2025

I personally think that the lint as is probably fits a restriction lint best (and should be documented to only show possible optimization opportunities), however, with an additional check if the result of that method ever is manipulated other than iterating it, and only linting if that's not the case would make the lint vastly more useful.

However, I don't think this PR needs to do the extension.

Copy link
Contributor

@llogiq llogiq left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like the starting point of a cool lint, but I'm afraid it's not quite there yet.

I propose we either put it in nursery until we make it less false-positive-happy or extend it to check whether there is actually a caller of the linted item that only iterates on the result.

View changes since this review

visitor.visit_ty_unambig(self_ty);

!visitor.all_lts().is_empty()
visitor.all_lts().next().is_some()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand how this is better than is_empty() here. You could also use impl Iterator<Item = Lifettime> + ExactSizeIterator.

Copy link
Contributor Author

@bend-n bend-n Nov 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you avoid iterating all of the items just to check the length, and it doesnt impl exactsizeiterator

// walk the type F, it may not contain LT refs
walk_unambig_ty(&mut visitor, pred.bounded_ty);
if !visitor.all_lts().is_empty() {
if visitor.all_lts().next().is_some() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

/// Given a type, collect all of its generic parameters.
/// Example: `MyStruct<P, Box<Q, R>>` => `vec![P, Q, R]`
fn collect_generic_params(ty: Ty<'_>) -> Vec<Ty<'_>> {
fn collect_generic_params(ty: Ty<'_>) -> impl Iterator<Item = Ty<'_>> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it make sense to rename this into iter_generic_params?

///
/// ### Why is this bad?
///
/// This might not be necessary, and its more idiomatic to simply return the iterator, giving the caller the option to use it as they see fit.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree that it's more idiomatic to return the iterator. I have certainly seen and written a lot of methods that return Vec via collect. I will gladly admit that omitting the collect may sometimes benefit performance – if the allocation can be removed, which depends on the callers.

Also I would advice avoiding the word "simply". It can come off as condescending. I don't know what is simple for any person reading this, and I shouldn't want to guess.

///
/// ### Potential Issues
///
/// In some cases, there may be some lifetimes attached to your iterator that make it difficult, or even impossible, to avoid a collection.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also if no caller can do without allocating a Vec, all you have done by applying this lint is moving one collect call into multiple other locations. Worse, even if there is one call that doesn't need to collect, the additional code produced may well negate any performance benefit gained by drowning out the L1 cache of hot path code, if there are multiple callers outside hot code paths.

Comment on lines 54 to 58
pub fn new(conf: &'static Conf) -> Self {
Self {
avoid_breaking_exported_api: conf.avoid_breaking_exported_api,
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A nice trick one can pull because function signatures are patterns:

Suggested change
pub fn new(conf: &'static Conf) -> Self {
Self {
avoid_breaking_exported_api: conf.avoid_breaking_exported_api,
}
}
pub fn new(Conf { avoid_breaking_exported_api, .. }: &'static Conf) -> Self {
Self { avoid_breaking_exported_api }
}
&& let ExprKind::MethodCall(path, iter_expr, args, call_span) = expr.kind
&& !(self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(def_id))
&& !args.iter().any(|e| e.span.from_expansion())
// && !iter_expr.span.from_expansion()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove commented out code.

/// Returns all the comments a given span contains.
///
/// Comments are returned wrapped with their relevant delimiters.
#[allow(clippy::unnecessary_collect)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That we have three allows in our code base should be testament to the fact that this lint is a bit too false-positive-happy to be widely useful as is.

Luckily, it is certainly possible to improve it, so that shouldn't discourage you.

@bend-n bend-n force-pushed the returning_vector_produced_from_from_iterator branch from c0d681d to cae7fc7 Compare November 15, 2025 11:49
@rustbot
Copy link
Collaborator

rustbot commented Nov 15, 2025

This PR was rebased onto a different master commit. Here's a range-diff highlighting what actually changed.

Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.

@bend-n bend-n force-pushed the returning_vector_produced_from_from_iterator branch from cae7fc7 to 5834ade Compare November 15, 2025 11:52
@bend-n bend-n force-pushed the returning_vector_produced_from_from_iterator branch from 5834ade to b0cac50 Compare November 15, 2025 11:56
@bend-n bend-n force-pushed the returning_vector_produced_from_from_iterator branch from b0cac50 to 7e91e18 Compare November 15, 2025 12:03
@bend-n bend-n requested a review from llogiq December 11, 2025 20:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-fcp S-waiting-on-review Status: Awaiting review from the assignee but also interested parties

8 participants