Is it safe to acquire an `&'static T` from a thread local storage of `T` if `T: !Sync`?

In other words, is this function safe?

#![feature(negative_bounds)]

use std::mem;
use std::thread::LocalKey;

pub fn get_thread_local_static_ref<T>(key: &'static LocalKey<T>) -> &'static T
where
    T: !Sync,
{
    key.with(|value| unsafe { mem::transmute(value) })
}

If T is !Sync, &T is !Send, so although &'static T have static lifetime, you can only use it in the current thread.

OK, the Drop implementation maybe an issue, because it gains mutable access to the object, while it can also acquire an immutable reference to the object, which breaks the borrow-check rule.

It would also allow you to get a &'static _ to any fields, which may be Sync.

1 Like

What if the type is a specific type that is carefully implemented that guarantees no drop reentrant and no references to Sync types can be acquired from the value?

In addition to what others said here, when the thread will die you will have a problem.

What is the problem? !Sync prevents its reference from being sent to another thread, and it does not return borrowed values that is Sync. When the thread dies, no other thread can have references to the value of part of the value.

Even if the reference never escapes the thread you could have another thread local on the same thread hold a reference to your thread local. If then your thread local is dropped first and the other thread local access yours in its Drop implementation then you'll have a use-after-free.

Here is an example where the main thread ends up obtaining a reference to part of the value of another thread's thread-local variable (playground):


pub struct MyStr {
    s: OnceCell<String>,
    _p: *const u8,
}

impl MyStr {
    fn new() -> Self {
        MyStr { s: OnceCell::new(), _p: std::ptr::null() }
    }
}

thread_local! {
    pub static FOO: MyStr = MyStr::new();
}

fn main() {
    let s = std::thread::spawn(|| {
        let foo = get_thread_local_static_ref(&FOO);
        let s = foo.s.get_or_init(|| "hello world".to_string()).as_str();
        assert_eq!(s, "hello world");
        s
    })
    .join()
    .unwrap();
    println!("Accessing string from joined thread: {s:?}");
}

(The purpose of the field _p is to ensure that MyStr: !Sync.)

I think your example implicitly borrowed &String from &MyStr, which my constraints excludes. The problem @SkiFire13 mentioned seems to be more serious.

I don't understand. Which constraints rule that out?

I meant the “no references to Sync types can be acquired from the value” constraint.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.