Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions library/std/src/sys/thread_local/native/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,10 @@ 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.
// NOTE: The `PhantomData` here is a nice trick to ensure that *all* usages of `$t` can be
// inferred to have static lifetimes. This function definition would normally *not* be such
// a definition, but because lifetime elision tends to prefer lifetimes matching arguments
// to a function, the `'static` lifetime inside the `PhantomData` qualifies here.
Comment on lines +84 to +87

@RalfJung RalfJung Sep 27, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
// NOTE: The `PhantomData` here is a nice trick to ensure that *all* usages of `$t` can be
// inferred to have static lifetimes. This function definition would normally *not* be such
// a definition, but because lifetime elision tends to prefer lifetimes matching arguments
// to a function, the `'static` lifetime inside the `PhantomData` qualifies here.
// NOTE: The `PhantomData` here is a nice trick to ensure that *all* usages of `$t` can be
// inferred to have static lifetimes: if `$t` has missing lifetimes, like `&str`,
// then lifetime elision will pick up the only lifetime in the argument,
// `'static`. The we type-check `$init` against that expanded type.

View changes since the review

#[allow(mismatched_lifetime_syntaxes)]
#[inline]
fn __rust_std_internal_init_fn(_lifetime_elision: $crate::marker::PhantomData<&'static ()>) -> $t {
Expand All @@ -92,10 +93,12 @@ pub macro thread_local_inner {

unsafe {
$crate::thread::LocalKey::new(const {
// NOTE: `$t`'s lifetimes can always be inferred in turbofish position
if $crate::mem::needs_drop::<$t>() {
|__rust_std_internal_init| {
#[thread_local]
$(#[$align_attr])*
// NOTE: `$t`'s lifetimes can always be inferred `'static` for `static`s
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($crate::marker::PhantomData))
Expand All @@ -104,6 +107,7 @@ pub macro thread_local_inner {
|__rust_std_internal_init| {
#[thread_local]
$(#[$align_attr])*
// NOTE: `$t`'s lifetimes can always be inferred `'static` for `static`s
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($crate::marker::PhantomData))
Expand Down
9 changes: 6 additions & 3 deletions library/std/src/sys/thread_local/os.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ 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.
// NOTE: The `PhantomData` here is a nice trick to ensure that *all* usages of `$t` can be
// inferred to have static lifetimes. This function definition would normally *not* be such
// a definition, but because lifetime elision tends to prefer lifetimes matching arguments
// to a function, the `'static` lifetime inside the `PhantomData` qualifies here.
#[allow(mismatched_lifetime_syntaxes)]
#[inline]
fn __rust_std_internal_init_fn(_lifetime_elision: $crate::marker::PhantomData<&'static ()>) -> $t { $init }
Expand All @@ -32,6 +33,7 @@ pub macro thread_local_inner {
// in `tests/thread.rs` if these types are renamed.
unsafe {
$crate::thread::LocalKey::new(|__rust_std_internal_init| {
// NOTE: `$t`'s lifetimes can always be inferred `'static` for `static`s
static __RUST_STD_INTERNAL_VAL: $crate::thread::local_impl::Storage<$t, {
$({
// Ensure that attributes have valid syntax
Expand All @@ -42,6 +44,7 @@ pub macro thread_local_inner {
})?

#[allow(unused_mut)]
// NOTE: `$t`'s lifetimes can always be inferred in turbofish position
let mut final_align = $crate::thread::local_impl::value_align::<$t>();
$($($crate::thread::local_impl::thread_local_inner!(@align final_align, $($align_attr)*);)+)?
final_align
Expand Down
18 changes: 18 additions & 0 deletions tests/ui/thread-local/non-static-lifetimes.rs

@clarfonthey clarfonthey Sep 26, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

cc @RalfJung since you mentioned having tests for this. Admittedly, I kind of struggled to come up with anything for this that might be even remotely sensible (and these aren't that), but, please feel free to comment whether this is satisfactory.

View changes since the review

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yeah that's fair, I wasn't sure either. It just felt like something we should test for.

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use std::borrow::Cow;

fn main() {
let x = "";
std::thread_local!(static A: &str = x);
//~^ ERROR attempt to use a non-constant value in a constant [E0435]
}

fn test<'a>(x: &'a str) {
std::thread_local!(static A: Cow<'a, str> = Cow::Borrowed(""));
//~^ ERROR can't use generic parameters from outer item [E0401]
//~| ERROR can't use generic parameters from outer item [E0401]
//~| ERROR can't use generic parameters from outer item [E0401]
//~| ERROR can't use generic parameters from outer item [E0401]
//~| ERROR can't use generic parameters from outer item [E0401]

// wow, aren't macros great?
}
61 changes: 61 additions & 0 deletions tests/ui/thread-local/non-static-lifetimes.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
error[E0435]: attempt to use a non-constant value in a constant
--> $DIR/non-static-lifetimes.rs:5:41
|
LL | std::thread_local!(static A: &str = x);
| - ^
| |
| this would need to be a `let`

error[E0401]: can't use generic parameters from outer item
--> $DIR/non-static-lifetimes.rs:10:38
|
LL | fn test<'a>(x: &'a str) {
| -- lifetime parameter from outer item
LL | std::thread_local!(static A: Cow<'a, str> = Cow::Borrowed(""));
| ^^ use of generic parameter from outer item

error[E0401]: can't use generic parameters from outer item
--> $DIR/non-static-lifetimes.rs:10:38
|
LL | fn test<'a>(x: &'a str) {
| -- lifetime parameter from outer item
LL | std::thread_local!(static A: Cow<'a, str> = Cow::Borrowed(""));
| ---------------------------------^^---------------------------
| | |
| | use of generic parameter from outer item
| lifetime `'a` is missing in item created through this procedural macro

error[E0401]: can't use generic parameters from outer item
--> $DIR/non-static-lifetimes.rs:10:38
|
LL | fn test<'a>(x: &'a str) {
| -- lifetime parameter from outer item
LL | std::thread_local!(static A: Cow<'a, str> = Cow::Borrowed(""));
| ^^ use of generic parameter from outer item
|
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`

error[E0401]: can't use generic parameters from outer item
--> $DIR/non-static-lifetimes.rs:10:38
|
LL | fn test<'a>(x: &'a str) {
| -- lifetime parameter from outer item
LL | std::thread_local!(static A: Cow<'a, str> = Cow::Borrowed(""));
| ^^ use of generic parameter from outer item
|
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`

error[E0401]: can't use generic parameters from outer item
--> $DIR/non-static-lifetimes.rs:10:38
|
LL | fn test<'a>(x: &'a str) {
| -- lifetime parameter from outer item
LL | std::thread_local!(static A: Cow<'a, str> = Cow::Borrowed(""));
| ^^ use of generic parameter from outer item
|
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`

error: aborting due to 6 previous errors

Some errors have detailed explanations: E0401, E0435.
For more information about an error, try `rustc --explain E0401`.
Loading