From ea48beaf1bb64904ec11fc7382989f60ee5f64d6 Mon Sep 17 00:00:00 2001 From: Zachary S Date: Sun, 19 Jul 2026 07:40:22 -0500 Subject: [PATCH 1/3] Allow elided ('static) lifetimes in `#[thread_local]` with or without a const initializer, on all platforms. --- library/std/src/sys/thread_local/native/mod.rs | 10 +++++++--- library/std/src/sys/thread_local/no_threads.rs | 8 ++++++-- library/std/src/sys/thread_local/os.rs | 8 ++++++-- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/library/std/src/sys/thread_local/native/mod.rs b/library/std/src/sys/thread_local/native/mod.rs index 4dad81685a94e..209a8790def08 100644 --- a/library/std/src/sys/thread_local/native/mod.rs +++ b/library/std/src/sys/thread_local/native/mod.rs @@ -81,8 +81,12 @@ pub macro thread_local_inner { // used to generate the `LocalKey` value for `thread_local!` (@key $t:ty, $(#[$align_attr:meta])*, $init:expr) => {{ + // We intentionally have an argument-position `'static` lifetime so that elided lifetimes in `$t` + // become `'static` like they do for `const`s and `static`s, including in the other two + // `thread_local!` implementations. + #[allow(mismatched_lifetime_syntaxes)] #[inline] - fn __rust_std_internal_init_fn() -> $t { + fn __rust_std_internal_init_fn(_lifetime_elision: $crate::marker::PhantomData<&'static ()>) -> $t { $init } @@ -94,7 +98,7 @@ pub macro thread_local_inner { $(#[$align_attr])* static __RUST_STD_INTERNAL_VAL: $crate::thread::local_impl::LazyStorage<$t, ()> = $crate::thread::local_impl::LazyStorage::new(); - __RUST_STD_INTERNAL_VAL.get_or_init(__rust_std_internal_init, __rust_std_internal_init_fn) + __RUST_STD_INTERNAL_VAL.get_or_init(__rust_std_internal_init, || __rust_std_internal_init_fn($crate::marker::PhantomData)) } } else { |__rust_std_internal_init| { @@ -102,7 +106,7 @@ pub macro thread_local_inner { $(#[$align_attr])* static __RUST_STD_INTERNAL_VAL: $crate::thread::local_impl::LazyStorage<$t, !> = $crate::thread::local_impl::LazyStorage::new(); - __RUST_STD_INTERNAL_VAL.get_or_init(__rust_std_internal_init, __rust_std_internal_init_fn) + __RUST_STD_INTERNAL_VAL.get_or_init(__rust_std_internal_init, || __rust_std_internal_init_fn($crate::marker::PhantomData)) } } }) diff --git a/library/std/src/sys/thread_local/no_threads.rs b/library/std/src/sys/thread_local/no_threads.rs index f9d0ed384fe24..0927595280f1f 100644 --- a/library/std/src/sys/thread_local/no_threads.rs +++ b/library/std/src/sys/thread_local/no_threads.rs @@ -31,14 +31,18 @@ pub macro thread_local_inner { // used to generate the `LocalKey` value for `thread_local!` (@key $t:ty, $(#[$align_attr:meta])*, $init:expr) => {{ + // We intentionally have an argument-position `'static` lifetime so that elided lifetimes in `$t` + // become `'static` like they do for `const`s and `static`s, including in the other two + // `thread_local!` implementations. + #[allow(mismatched_lifetime_syntaxes)] #[inline] - fn __rust_std_internal_init_fn() -> $t { $init } + fn __rust_std_internal_init_fn(_lifetime_elision: $crate::marker::PhantomData<&'static ()>) -> $t { $init } unsafe { $crate::thread::LocalKey::new(|__rust_std_internal_init| { $(#[$align_attr])* static __RUST_STD_INTERNAL_VAL: $crate::thread::local_impl::LazyStorage<$t> = $crate::thread::local_impl::LazyStorage::new(); - __RUST_STD_INTERNAL_VAL.get(__rust_std_internal_init, __rust_std_internal_init_fn) + __RUST_STD_INTERNAL_VAL.get(__rust_std_internal_init, || __rust_std_internal_init_fn($crate::marker::PhantomData)) }) } }}, diff --git a/library/std/src/sys/thread_local/os.rs b/library/std/src/sys/thread_local/os.rs index bc044aafa983c..289ac33bb7b76 100644 --- a/library/std/src/sys/thread_local/os.rs +++ b/library/std/src/sys/thread_local/os.rs @@ -20,8 +20,12 @@ pub macro thread_local_inner { // used to generate the `LocalKey` value for `thread_local!`. (@key $t:ty, $($(#[$($align_attr:tt)*])+)?, $init:expr) => {{ + // We intentionally have an argument-position `'static` lifetime so that elided lifetimes in `$t` + // become `'static` like they do for `const`s and `static`s, including in the other two + // `thread_local!` implementations. + #[allow(mismatched_lifetime_syntaxes)] #[inline] - fn __rust_std_internal_init_fn() -> $t { $init } + fn __rust_std_internal_init_fn(_lifetime_elision: $crate::marker::PhantomData<&'static ()>) -> $t { $init } // NOTE: this cannot import `LocalKey` or `Storage` with a `use` because that can shadow // user provided type or type alias with a matching name. Please update the shadowing test @@ -43,7 +47,7 @@ pub macro thread_local_inner { final_align }> = $crate::thread::local_impl::Storage::new(); - __RUST_STD_INTERNAL_VAL.get(__rust_std_internal_init, __rust_std_internal_init_fn) + __RUST_STD_INTERNAL_VAL.get(__rust_std_internal_init, || __rust_std_internal_init_fn($crate::marker::PhantomData)) }) } }}, From e3532e092fce8693e5f7cfd1c3125bf4a825d2a7 Mon Sep 17 00:00:00 2001 From: Zachary S Date: Sun, 19 Jul 2026 08:54:25 -0500 Subject: [PATCH 2/3] Remove tests/ui/suggestions/missing-lifetime-specifier.rs It was testing diagnostics emitted for missing lifetime specifiers in `thread_local!`, but now elided lifetimes in `thread_local!` are `'static`, so there's nothing to test. --- .../suggestions/missing-lifetime-specifier.rs | 54 --------- .../missing-lifetime-specifier.stderr | 109 ------------------ 2 files changed, 163 deletions(-) delete mode 100644 tests/ui/suggestions/missing-lifetime-specifier.rs delete mode 100644 tests/ui/suggestions/missing-lifetime-specifier.stderr diff --git a/tests/ui/suggestions/missing-lifetime-specifier.rs b/tests/ui/suggestions/missing-lifetime-specifier.rs deleted file mode 100644 index 93416a9bc2ac7..0000000000000 --- a/tests/ui/suggestions/missing-lifetime-specifier.rs +++ /dev/null @@ -1,54 +0,0 @@ -// The specific errors produced depend the thread-local implementation. -// Run only on platforms with "fast" TLS. -//@ ignore-wasm globals are used instead of thread locals -//@ ignore-emscripten globals are used instead of thread locals -//@ ignore-android does not use #[thread_local] -//@ ignore-nto does not use #[thread_local] -//@ ignore-qnx does not use #[thread_local] -// Different number of duplicated diagnostics on different targets -//@ compile-flags: -Zdeduplicate-diagnostics=yes - -use std::cell::RefCell; -use std::collections::HashMap; - -pub union Foo<'t, 'k> { - i: &'t i64, - f: &'k f64, -} -trait Bar<'t, 'k> {} - -pub union Qux<'t, 'k, I> { - i: &'t I, - f: &'k I, -} -trait Tar<'t, 'k, I> {} - -thread_local! { - static a: RefCell>>> = RefCell::new(HashMap::new()); - //~^ ERROR missing lifetime specifiers -} -thread_local! { - static b: RefCell>>> = RefCell::new(HashMap::new()); - //~^ ERROR missing lifetime specifiers -} -thread_local! { - static c: RefCell>>>> = RefCell::new(HashMap::new()); - //~^ ERROR missing lifetime specifiers -} -thread_local! { - static d: RefCell>>>> = RefCell::new(HashMap::new()); - //~^ ERROR missing lifetime specifiers -} - -thread_local! { - static e: RefCell>>>> = RefCell::new(HashMap::new()); - //~^ ERROR union takes 2 lifetime arguments but 1 lifetime argument -} -thread_local! { - static f: RefCell>>>> = - RefCell::new(HashMap::new()); - //~^^ ERROR trait takes 2 lifetime arguments but 1 lifetime argument was supplied - //~| ERROR missing lifetime specifier -} - -fn main() {} diff --git a/tests/ui/suggestions/missing-lifetime-specifier.stderr b/tests/ui/suggestions/missing-lifetime-specifier.stderr deleted file mode 100644 index 4c50f2a43e270..0000000000000 --- a/tests/ui/suggestions/missing-lifetime-specifier.stderr +++ /dev/null @@ -1,109 +0,0 @@ -error[E0106]: missing lifetime specifiers - --> $DIR/missing-lifetime-specifier.rs:27:44 - | -LL | static a: RefCell>>> = RefCell::new(HashMap::new()); - | ^^^ expected 2 lifetime parameters - | - = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from -help: consider using the `'static` lifetime, but this is uncommon unless you're returning a borrowed value from a `const` or a `static`, or if you will only have owned values - | -LL | static a: RefCell>>>> = RefCell::new(HashMap::new()); - | ++++++++++++++++++ - -error[E0106]: missing lifetime specifiers - --> $DIR/missing-lifetime-specifier.rs:31:44 - | -LL | static b: RefCell>>> = RefCell::new(HashMap::new()); - | ^ ^^^ expected 2 lifetime parameters - | | - | expected named lifetime parameter - | - = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from -help: consider using the `'static` lifetime, but this is uncommon unless you're returning a borrowed value from a `const` or a `static` - | -LL | static b: RefCell>>>> = RefCell::new(HashMap::new()); - | +++++++ ++++++++++++++++++ - -error[E0106]: missing lifetime specifiers - --> $DIR/missing-lifetime-specifier.rs:35:47 - | -LL | static c: RefCell>>>> = RefCell::new(HashMap::new()); - | ^ expected 2 lifetime parameters - | - = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from -help: consider using the `'static` lifetime, but this is uncommon unless you're returning a borrowed value from a `const` or a `static`, or if you will only have owned values - | -LL | static c: RefCell>>>> = RefCell::new(HashMap::new()); - | +++++++++++++++++ - -error[E0106]: missing lifetime specifiers - --> $DIR/missing-lifetime-specifier.rs:39:44 - | -LL | static d: RefCell>>>> = RefCell::new(HashMap::new()); - | ^ ^ expected 2 lifetime parameters - | | - | expected named lifetime parameter - | - = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from -help: consider using the `'static` lifetime, but this is uncommon unless you're returning a borrowed value from a `const` or a `static` - | -LL | static d: RefCell>>>> = RefCell::new(HashMap::new()); - | +++++++ +++++++++++++++++ - -error[E0106]: missing lifetime specifier - --> $DIR/missing-lifetime-specifier.rs:48:44 - | -LL | static f: RefCell>>>> = - | ^ expected named lifetime parameter - | - = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from -help: consider using the `'static` lifetime, but this is uncommon unless you're returning a borrowed value from a `const` or a `static` - | -LL | static f: RefCell>>>> = - | +++++++ -help: instead, you are more likely to want to return an owned value - | -LL - static f: RefCell>>>> = -LL + static f: RefCell>>>> = - | - -error[E0107]: union takes 2 lifetime arguments but 1 lifetime argument was supplied - --> $DIR/missing-lifetime-specifier.rs:44:44 - | -LL | static e: RefCell>>>> = RefCell::new(HashMap::new()); - | ^^^ ------- supplied 1 lifetime argument - | | - | expected 2 lifetime arguments - | -note: union defined here, with 2 lifetime parameters: `'t`, `'k` - --> $DIR/missing-lifetime-specifier.rs:20:11 - | -LL | pub union Qux<'t, 'k, I> { - | ^^^ -- -- -help: add missing lifetime argument - | -LL | static e: RefCell>>>> = RefCell::new(HashMap::new()); - | +++++++++ - -error[E0107]: trait takes 2 lifetime arguments but 1 lifetime argument was supplied - --> $DIR/missing-lifetime-specifier.rs:48:49 - | -LL | static f: RefCell>>>> = - | ^^^ ------- supplied 1 lifetime argument - | | - | expected 2 lifetime arguments - | -note: trait defined here, with 2 lifetime parameters: `'t`, `'k` - --> $DIR/missing-lifetime-specifier.rs:24:7 - | -LL | trait Tar<'t, 'k, I> {} - | ^^^ -- -- -help: add missing lifetime argument - | -LL | static f: RefCell>>>> = - | +++++++++ - -error: aborting due to 7 previous errors - -Some errors have detailed explanations: E0106, E0107. -For more information about an error, try `rustc --explain E0106`. From e423836114c8ae49b2d500cc3e8276970e555b62 Mon Sep 17 00:00:00 2001 From: Zachary S Date: Tue, 21 Jul 2026 12:28:27 -0500 Subject: [PATCH 3/3] Add test for thread_local! lifetime elision. --- tests/ui/thread-local/lifetime-elision.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 tests/ui/thread-local/lifetime-elision.rs diff --git a/tests/ui/thread-local/lifetime-elision.rs b/tests/ui/thread-local/lifetime-elision.rs new file mode 100644 index 0000000000000..3b960d18764cb --- /dev/null +++ b/tests/ui/thread-local/lifetime-elision.rs @@ -0,0 +1,14 @@ +// Test that `thread_local!` defaults elided lifetimes in the type to `'static`. +// Regression test for 159358. +// Ideally this should be tested at least one target using each `thread_local!` +// implementation (no-threads, native, os), but currently there are no Tier 1 `no-threads` +// targets. `x86_64-unknown-linux-gnu` is a `native` target and `x86_64-pc-windows-gnu` +// is a `os` target, so those are covered (as of 2026-07-21). +//@ check-pass + +// Const initializer +std::thread_local!(static A: &str = const { "" }); +// Non-const initializer +std::thread_local!(static B: &str = ""); + +fn main() {}