rust-lang / rust-lang/rust-clippy
`missing_const_for_thread_local` false positive on targets without `#[thread_local]`: fires on already-`const` initializers
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 13.5k
- Forks
- 2.2k
- Avg merge
- 2d 10h
- Merged PRs (30d)
- 32
Description
Summary
On targets without native #[thread_local] support (no target_thread_local cfg — e.g. x86_64-pc-windows-gnu), missing_const_for_thread_local fires on thread_local! statics whose initializer is already a const block, spanning the whole macro invocation.
The trigger is a std expansion change between stable 1.97 and current nightlies: on the os-based TLS path, a const initializer now expands to a plain (non-const) init fn (-Zunpretty=expanded on the reproducer below):
#[inline]
fn __rust_std_internal_init_fn() -> Cell<Option<u8>> {
const { Cell::new(None) }
}
The lint already anticipates init-fn-generating backends and skips when the generated fn is const (the !cx.tcx.is_const_fn(defid) guard from #12276) — but this generated fn is no longer const, so the guard doesn't catch it, is_min_const_fn approves the body, and the lint fires with the whole macro invocation as the span.
Observed consequences on x86_64-pc-windows-gnu with nightly-2026-08-07 (stable 1.97.1 is not affected, so this reaches stable with 1.99 if it stays):
- the reduced example below warns spuriously, with the lint on by default;
cargo test --test dogfoodfails on a master checkout: the lint fires onclippy_utils/src/macros.rs:205, whose initializer is alreadyconst { Cell::new(None) };tests/ui/missing_const_for_thread_local.rsproduces 8 errors instead of the blessed 6 (extra whole-block-span errors for the two already-conststatics).
CI cannot see this: the Windows job tests x86_64-pc-windows-msvc only, and every current CI host has target_thread_local.
Lint Name
missing_const_for_thread_local
Reproducer
I tried this code:
use std::cell::Cell;
thread_local! {
static ALREADY_CONST: Cell<Option<u8>> = const { Cell::new(None) };
}
fn main() { ALREADY_CONST.with(|c| c.get()); }
I saw this happen (cargo clippy, host x86_64-pc-windows-gnu):
warning: initializer for `thread_local` value can be made `const`
--> src\main.rs:2:1
|
2 | / thread_local! {
3 | | static ALREADY_CONST: Cell<Option<u8>> = const { Cell::new(None) };
4 | | }
| |_^
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#missing_const_for_thread_local
= note: `#[warn(clippy::missing_const_for_thread_local)]` on by default
I expected to see this happen: no warning — the initializer is already const, there is nothing to suggest. On x86_64-pc-windows-msvc with the same nightly there is no warning.
Version
rustc 1.99.0-nightly (84b36a78a 2026-08-06)
binary: rustc
commit-hash: 84b36a78a28a63f134171c670be1932ffa2485f8
commit-date: 2026-08-06
host: x86_64-pc-windows-gnu
release: 1.99.0-nightly
LLVM version: 23.1.0
Additional Labels
@rustbot label +C-bug +I-false-positive
A small fix (skip ExprKind::ConstBlock initializers before the MIR check) removes the two extra ui-test errors and un-breaks dogfood on this host; PR to follow.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with tests/ui/missing_const_for_thread_local.rs and the missing_const_for_thread_local lint's initializer handling, then inspect the existing dogfood case at clippy_utils/src/macros.rs:205. Verify the change removes the two spurious diagnostics, restores the expected six UI-test errors, and leaves already-const initializers without warnings.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- tooling
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100