Skip to content

Clear collection reassignment#16549

Open
yash-kumarx wants to merge 5 commits intorust-lang:masterfrom
yash-kumarx:clear-collection-reassignment
Open

Clear collection reassignment#16549
yash-kumarx wants to merge 5 commits intorust-lang:masterfrom
yash-kumarx:clear-collection-reassignment

Conversation

@yash-kumarx
Copy link

@yash-kumarx yash-kumarx commented Feb 10, 2026

Add clear_instead_of_new lint

What does this PR do?

Adds a new perf lint that detects when a collection is reassigned to a new empty collection instead of calling .clear().

Why?

Reassigning a collection with collection = CollectionType::new() is inefficient because it:-

  1. Deallocates the existing collection
  2. Loses any pre-allocated capacity
  3. Allocates a new collection from scratch

Advantage

Using .clear() is more efficient as it:-

  1. Keeps the existing allocation and capacity
  2. Only removes the elements
  3. Avoids unnecessary deallocation/reallocation overhead
  • This can lead to significant performance improvements in loops or many paths where collections are frequently reset.

What collections are supported?

  • Vec
  • HashMap
  • HashSet
  • VecDeque
  • BTreeMap
  • BTreeSet
  • BinaryHeap
  • LinkedList

Example
Before:

rustlet mut v = Vec::new();
v.push(1);
v = Vec::new(); // Inefficient: deallocates and reallocates

After:

rustlet mut v = Vec::new();
v.push(1);
v.clear(); // Efficient: reuses existing allocation

Fixes #16520

changelog: [clear_instead_of_new]: new lint to suggest using .clear() instead of reassigning empty collections

…clear() instead of reassigning a new empty collection, which is more efficient as it avoids unnecessary deallocation and reallocation.
@rustbot rustbot added needs-fcp S-waiting-on-review Status: Awaiting review from the assignee but also interested parties labels Feb 10, 2026
@rustbot
Copy link
Collaborator

rustbot commented Feb 10, 2026

r? @dswij

rustbot has assigned @dswij.
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

Why was this reviewer chosen?

The reviewer was selected based on:

  • Owners of files modified in this PR: 7 candidates
  • 7 candidates expanded to 7 candidates
  • Random selection from Jarcho, dswij, llogiq, samueltardieu

@rustbot

This comment has been minimized.

@github-actions
Copy link

github-actions bot commented Feb 10, 2026

Lintcheck changes for 2a5e173

Lint Added Removed Changed
clippy::clear_instead_of_new 7 0 0

This comment will be updated if you push new changes

Copy link
Member

@samueltardieu samueltardieu left a comment

Choose a reason for hiding this comment

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

This is a good first step, but this requires changes:

  • Since this lint only triggers on call to something::new(), it should probably be part of the clippy_lints::methods module.
  • You should add tests showing that the lint will not trigger if the code belongs to a macro (this will probably be taken care of automatically once integrated into the methods module).
  • You should also add more complex tests such as *v = Vec::new();, as it should be replaced by (*v).clear(); or, even better, v.clear(); instead.

We usually avoid comparing strings in Clippy. Method names are compared using symbols (sym::new()), and types names usually use diagnostic items.

r? samueltardieu
@rustbot author

View changes since this review

Comment on lines 63 to 83
fn is_collection_new_call(cx: &LateContext<'_>, func: &Expr<'_>) -> bool {
if let ExprKind::Path(QPath::TypeRelative(ty, method)) = func.kind
&& method.ident.name.as_str() == "new"
{
let ty = cx.typeck_results().node_type(ty.hir_id);
let type_name = ty.to_string();

// Check if it's one of the standard collections
// Type names come in the format "std::vec::Vec<T>" or "std::collections::HashMap<K, V>"
type_name.contains("::Vec<")
|| type_name.contains("::HashMap<")
|| type_name.contains("::HashSet<")
|| type_name.contains("::VecDeque<")
|| type_name.contains("::BTreeMap<")
|| type_name.contains("::BTreeSet<")
|| type_name.contains("::BinaryHeap<")
|| type_name.contains("::LinkedList<")
} else {
false
}
}
Copy link
Member

Choose a reason for hiding this comment

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

This is not the proper way of checking for the type, we don't use string matching in Clippy as this is not robust. You should look at the diagnostic items page in the compiler dev guide, and use functions from clippy_utils to check for them.

/// vec.push(1);
/// vec.clear();
/// ```
#[clippy::version = "1.XX.0"]
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
#[clippy::version = "1.XX.0"]
#[clippy::version = "1.95.0"]

/// ```no_run
/// let mut vec = Vec::new();
/// vec.push(1);
/// vec = Vec::new();
Copy link
Member

Choose a reason for hiding this comment

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

You should insert a f(&vec) in the middle, otherwise it makes little sense to push an element and erase the collection afterwards without using it.

@rustbot rustbot assigned samueltardieu and unassigned dswij Feb 10, 2026
@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties labels Feb 10, 2026
@rustbot
Copy link
Collaborator

rustbot commented Feb 10, 2026

Reminder, once the PR becomes ready for a review, use @rustbot ready.

@samueltardieu
Copy link
Member

Note that you can run the tests including the internal ones by using cargo test -F internal before pushing the PR.

@yash-kumarx
Copy link
Author

@samueltardieu Thanks for the review. I will work on these changes:

  1. Moving the lint to the methods module
  2. Use diagnostic items instead of string matching
  3. Add macro and dereference test cases
  4. Update version number

I will need a bit of time to study the methods module structure
and diagnostic items. Should have updates in a few days.

- Use diagnostic items instead of string matching for type checking
- Move lint to methods module as requested
- Add macro handling via in_external_macro check
- Support dereference patterns (*v = Vec::new() → v.clear())
- Change applicability to MaybeIncorrect
- Add comprehensive tests for all collection types
- Fix version to 1.95.0

Addresses all review comments from samueltardieu on PR rust-lang#16549
@rustbot

This comment has been minimized.


# Remove jujutsu directory from search tools
.jj
.DS_Store
Copy link
Member

Choose a reason for hiding this comment

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

This doesn't belong to this PR.

Copy link
Member

Choose a reason for hiding this comment

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

This file should not have been added

Copy link
Member

@samueltardieu samueltardieu left a comment

Choose a reason for hiding this comment

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

After applying those changes, could you also please:

  • squash your commits so that you only have two commits, one which applies the lint to only_used_in_recursion.rs and one which implements the lint;
  • remove the issue number from the commit message

View changes since this review

@@ -0,0 +1,152 @@
#![warn(clippy::clear_instead_of_new)]
#![allow(unused)]
Copy link
Member

Choose a reason for hiding this comment

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

This should not be needed:

Suggested change
#![allow(unused)]

Comment on lines +1 to 2
#![allow(clippy::clear_instead_of_new)]
#![allow(unused, clippy::useless_vec)]
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
#![allow(clippy::clear_instead_of_new)]
#![allow(unused, clippy::useless_vec)]
#![allow(clippy::clear_instead_of_new, clippy::useless_vec)]

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

Choose a reason for hiding this comment

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

Move it after the #![warn(…)]

Comment on lines +1 to 2
#![allow(clippy::clear_instead_of_new)]
#![allow(clippy::useless_vec, clippy::manual_repeat_n)]
Copy link
Member

Choose a reason for hiding this comment

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

Fuse the lines

}
}

#[allow(clippy::clear_instead_of_new)]
Copy link
Member

Choose a reason for hiding this comment

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

Please you #[expect] instead.

Comment on lines 19 to 22
let is_new_call = if let ExprKind::Call(func, args) = value.kind
&& args.is_empty()
&& let ExprKind::Path(QPath::TypeRelative(ty_path, method)) = func.kind
&& method.ident.name == sym::new
Copy link
Member

Choose a reason for hiding this comment

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

This will have been checked in the methods module check_expr() function already.

Comment on lines 55 to 75
// Get the type definition, peeling any references
let ty = ty.peel_refs();

// Check if it's one of the standard library collections
// We check for types that have a .clear() method
if let ty::Adt(adt, _) = ty.kind() {
let def_id = adt.did();

// Use diagnostic items to identify the type
// This is the proper way to check types in Clippy, not string matching
cx.tcx.is_diagnostic_item(sym::Vec, def_id)
|| cx.tcx.is_diagnostic_item(sym::HashMap, def_id)
|| cx.tcx.is_diagnostic_item(sym::HashSet, def_id)
|| cx.tcx.is_diagnostic_item(sym::VecDeque, def_id)
|| cx.tcx.is_diagnostic_item(sym::BTreeMap, def_id)
|| cx.tcx.is_diagnostic_item(sym::BTreeSet, def_id)
|| cx.tcx.is_diagnostic_item(sym::BinaryHeap, def_id)
|| cx.tcx.is_diagnostic_item(sym::LinkedList, def_id)
} else {
false
}
Copy link
Member

Choose a reason for hiding this comment

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

You can do this more efficiently with something like:

Suggested change
// Get the type definition, peeling any references
let ty = ty.peel_refs();
// Check if it's one of the standard library collections
// We check for types that have a .clear() method
if let ty::Adt(adt, _) = ty.kind() {
let def_id = adt.did();
// Use diagnostic items to identify the type
// This is the proper way to check types in Clippy, not string matching
cx.tcx.is_diagnostic_item(sym::Vec, def_id)
|| cx.tcx.is_diagnostic_item(sym::HashMap, def_id)
|| cx.tcx.is_diagnostic_item(sym::HashSet, def_id)
|| cx.tcx.is_diagnostic_item(sym::VecDeque, def_id)
|| cx.tcx.is_diagnostic_item(sym::BTreeMap, def_id)
|| cx.tcx.is_diagnostic_item(sym::BTreeSet, def_id)
|| cx.tcx.is_diagnostic_item(sym::BinaryHeap, def_id)
|| cx.tcx.is_diagnostic_item(sym::LinkedList, def_id)
} else {
false
}
matches!(ty.peel_refs().opt_diag_name(), Some(sym::HashMap | sym::HashSet | …))

Copy link
Member

Choose a reason for hiding this comment

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

Changes to this file don't seem to belong to this PR.

fn remove_by_id(&mut self, id: HirId) {
if let Some(param) = self.get_by_id_mut(id) {
param.uses = Vec::new();
param.uses.clear();
Copy link
Member

Choose a reason for hiding this comment

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

Could you split this into a separate commit that gets applied first (so that the tests always pass)?

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

Choose a reason for hiding this comment

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

Fuse with line 3.

- Use diagnostic items instead of string matching for type checking
- Move lint to methods module as requested
- Add macro handling via in_external_macro check
- Support dereference patterns (*v = Vec::new() → v.clear())
- Use get_diagnostic_name for efficient compiler query
- Change applicability to MaybeIncorrect
- Add comprehensive tests for all collection types

Addresses all review comments from samueltardieu on PR rust-lang#16549
@rustbot
Copy link
Collaborator

rustbot commented Feb 15, 2026

⚠️ Warning ⚠️

  • There are issue links (such as #123) in the commit messages of the following commits.
    Please move them to the PR description, to avoid spamming the issues with references to the commit, and so this bot can automatically canonicalize them to avoid issues with subtree.

@yash-kumarx
Copy link
Author

All the requested changes have been addressed:

  1. Moved to methods module
  2. Using diagnostic items instead of string matching
  3. Added macro handling
  4. Dereference patterns supported (*v = Vec::new()v.clear())
  5. Changed to MaybeIncorrect applicability
  6. Comprehensive tests added
  7. Version fixed to 1.95.0
  8. All tests passing

@rustbot ready

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties and removed S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) labels Feb 15, 2026
@yash-kumarx
Copy link
Author

@samueltardieu Thank you for the review. I'll make the changes accordingly

@samueltardieu samueltardieu added S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties labels Feb 15, 2026
@samueltardieu
Copy link
Member

Changed to MaybeIncorrect applicability

In which case can the applicability be incorrect?

@yash-kumarx
Copy link
Author

Changed to MaybeIncorrect applicability

In which case can the applicability be incorrect?

The applicability can be incorrect in uninitialized variables, different capacity semantics

For eg:-

let mut v: Vec<i32>;
v = Vec::new(); 

Lint suggests: v.clear()
But v.clear() would fail since v is uninitialized.

let mut v = vec![1, 2, 3, 4, 5];
v = Vec::new();
v.clear(); 

Vec::new() drops old vec, creates new one with capacity 0
But v.clear() keeps existing capacity (5)
If we want to free memory, Vec::new() is correct.

@samueltardieu
Copy link
Member

The applicability can be incorrect in uninitialized variables

It would be best for the lint not to trigger at all in this case (see clippy_utils::is_initialized().

different capacity semantics

This is a real one. Maybe an extra note could be added to the diagnostic saying that using .clear() will keep the collection capacity as-is.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-fcp S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Detect creation of new collection when it could be emptied

4 participants