Skip to content

Conversation

@Mr-Leshiy
Copy link

@Mr-Leshiy Mr-Leshiy commented Jan 25, 2026

Description

This PR introduces a new lint to notify users when a static item contains a type that implements the Drop trait.

As per the Rust Reference, static variables have a 'static lifetime and are never destroyed. Consequently, theDrop::drop method is never executed for these items.

Rational

In scenarios where drop is used for critical resource management. Such as closing file handles, releasing external locks, or performing a "graceful" shutdown of a subsystem, relying on a static item can lead to subtle resource leaks or unexpected behaviour. Since the programmer might expect the destructor to run at the end of the program's execution, providing a diagnostic when this won't happen helps prevent "silent" failures in resource cleanup.

changelog: new lint: drop_for_static

@rustbot rustbot added needs-fcp S-waiting-on-review Status: Awaiting review from the assignee but also interested parties labels Jan 25, 2026
@rustbot
Copy link
Collaborator

rustbot commented Jan 25, 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

@Mr-Leshiy Mr-Leshiy changed the title drop_for_static new lint New lint: drop_for_static Jan 25, 2026
@Jarcho
Copy link
Contributor

Jarcho commented Jan 25, 2026

This could be implemented as just:

if let ItemKind::Static(_, ident, ..) = item.kind
  && cx.tcx.type_of(item.owner_id.def_id).instantiate_identity().has_drop(cx.tcx, cx.typing_env())
{
  // lint
}

Note you should be using the ident's span when emitting the lint to avoid an overly large span being printed with the diagnostic.

@Mr-Leshiy
Copy link
Author

@Jarcho thank you for your quick response and suggestions !

Just tried your approach, but it does work recursively as I originally intended.
It catches only this scenario:

static A1: FooWithDrop = FooWithDrop;

and does not identify the presence of the Drop implementation in the rest of the scenarios which I've added as a test cases:

static A3: &FooWithDrop = &FooWithDrop;
static A5: (FooWithoutDrop, FooWithDrop) = (FooWithoutDrop, FooWithDrop);
static A7: [FooWithDrop; 1] = [FooWithDrop];
static A9: &[FooWithDrop] = &[FooWithDrop];
etc.

Suggested implementation.

if let ItemKind::Static(_, _, _, _) = item.kind
    && let ty = cx.tcx.type_of(item.owner_id.def_id).instantiate_identity()
    && has_drop(cx, ty)
{
   // lint
}

Or maybe I am missing something.

if let Some(def_id) = path.res.opt_def_id() {
let ty = self.cx.tcx.type_of(def_id).instantiate_identity();
if has_drop(self.cx, ty) {
self.drop_for_static_found = true;
Copy link
Contributor

Choose a reason for hiding this comment

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

It could be nice to store the span of at least the first such type, so that the final lint message could put a label1 on it -- something like:

error: static items with drop implementation
  --> tests/ui/drop_for_static.rs:36:8
   |
LL | static B1: Nested<FooWithDrop> = Nested(FooWithDrop, ());
   |        ^^         ^^^^^^^^^^^ type with drop implementation here

Or maybe just nest all the way and collect at all of them -- after all, that's what we end up doing when there isn't a type-with-drop. This would reduce the annoyance of the lint triggering again after you fix it

Footnotes

  1. using Diag::span_label

Copy link
Author

@Mr-Leshiy Mr-Leshiy Jan 26, 2026

Choose a reason for hiding this comment

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

Thank you for suggestion !
Already applied it.
But using span_label makes it like

LL | static A1: FooWithDrop = FooWithDrop;
   |        ^^  ----------- type with drop implementation here

Have not found an alternative to print exactly how you've mentioned.

Copy link
Contributor

Choose a reason for hiding this comment

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

Well done!

Don't worry about the underline type, I just misremembered it:)

Or maybe just nest all the way and collect at all of them -- after all, that's what we end up doing when there isn't a type-with-drop. This would reduce the annoyance of the lint triggering again after you fix it

I still think this would be a good idea.. let's see what the maintainers think

Copy link
Author

Choose a reason for hiding this comment

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

Yea, I was also thinking about that. And didn't do that because was concerned to not bee too annoying with the lint messages.
But in any case would be happy to add it as you mentioned.

@Mr-Leshiy Mr-Leshiy requested a review from ada4a January 26, 2026 15:53
Copy link
Contributor

@ada4a ada4a left a comment

Choose a reason for hiding this comment

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

One small thing, otherwise LGTM:)

Now we just need to wait for an actual maintainer

View changes since this review

@Jarcho
Copy link
Contributor

Jarcho commented Jan 26, 2026

Sorry, had had the wrong name. You want needs_drop. This won't catch references, but I don't think you want to do that in the first place since they don't do anything when dropped..

Co-authored-by: Ada Alakbarova <58857108+ada4a@users.noreply.github.com>
@Mr-Leshiy
Copy link
Author

Mr-Leshiy commented Jan 26, 2026

@Jarcho got it, will take a look how to use needs_drop.

This won't catch references, but I don't think you want to do that in the first place since they don't do anything when dropped..

I am not entirely agree, because it could be the following scenario, for which one we want to emit lint in my opinion.

struct FooWithDrop;

impl Drop for FooWithDrop {
    fn drop(&mut self) {}
}

static A: &FooWithDrop = &FooWithDrop;

@dswij
Copy link
Member

dswij commented Jan 27, 2026

r? clippy

@rustbot rustbot assigned flip1995 and unassigned dswij Jan 27, 2026
@github-actions
Copy link

github-actions bot commented Jan 27, 2026

Lintcheck changes for 2489ce9

Lint Added Removed Changed
clippy::drop_for_static 14 0 0

This comment will be updated if you push new changes

@Jarcho
Copy link
Contributor

Jarcho commented Jan 27, 2026

struct FooWithDrop;

impl Drop for FooWithDrop {
    fn drop(&mut self) {}
}

static A: &FooWithDrop = &FooWithDrop;

This case is not due to the type of the static, but due to lifetime extension in the initializer's body. Currently you'll lint on things like static FOO: Mutex<Option<&NeedsDrop>> = Mutex::new(None); which would be a false positive. Antoher false positive would be:

static FOO: NeedsDrop = NeedsDrop; // should lint
static FOO_REF: &NeedsDrop = &FOO; // shouldn't lint

Linting undropped temporaries in the initializer is fine, but that should probably be it's own lint.

@Mr-Leshiy
Copy link
Author

@Jarcho I see - agree, missed that.
Updating implementation ...

@Mr-Leshiy
Copy link
Author

@Jarcho so just to clarify
In these scenarios we want raise a lint

static A1: FooWithDrop = FooWithDrop;
static A2: (FooWithoutDrop, FooWithDrop) = (FooWithoutDrop, FooWithDrop);
static A3: [FooWithDrop; 1] = [FooWithDrop];
static A4: Nested<FooWithDrop> = Nested(FooWithDrop, ());
static A5: Nested<FooWithoutDrop, Nested<FooWithDrop>> = Nested(FooWithoutDrop, Nested(FooWithDrop, ()));
static A6: Nested<(FooWithoutDrop, FooWithDrop)> = Nested((FooWithoutDrop, FooWithDrop), ());
static A7: Nested<[FooWithDrop; 1]> = Nested([FooWithDrop], ());

And in these we dont

static A1: &FooWithDrop = &FooWithDrop;
static A2: &[FooWithDrop] = &[FooWithDrop];

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants