Why does LocalKey need a callback interface?

I poked around a while looking for documentation about this but no luck, the whole API is presented as fait accompli. Why does LocalKey, used as part of the thread_local! macro, use with() to expose its wrapped value instead of a guard or even a Deref implementation like most other synchronization mechanisms in std?

Since it's &'static LocalKey<T> you can get &'static T if it impls Deref. But TLS values don't exist after the thread is terminated so it can't be &'static

1 Like

That's the same problem as Mutex<T> though, which is why it uses a local MutexGuard<T> that implements Deref<T>.

Perhaps it's that forget()ing a thread-local guard would be unsafe?

That's probably it. Closure based guards are needed when leaking some type isn't safe. A leaked reference guard stops anything else from taking the lock, but dead lock isn't memory unsafe by Rust standards.

1 Like

No that's not the same problem. In case you have &'static Mutex<T> you can obtain MutexGuard<'static, T>. This is not true for LocalKey. The reason Mutex uses a guard is to release the lock.

LocalKey doesn't need to perform any cleanup after with().

1 Like

Ah, that's tricky!

Another contributing factor is how/when thread-local values are destroyed.

Normally, the OS will call destructor for a thread-local variable whenever your thread exits, however imagine I created a thread-local object (e.g. a logger) where the destructor accesses another thread-local object (e.g. imagine our logger writes messages to a thread-local buffer before sending them somewhere). If thread-locals were implemented as a 'static RAII guard, it would let you get a &'static T reference that can be stored in the logger, then if the buffer was destroyed before the logger, our logger would be left with a dangling reference and we'd have a bad time.

Using a callback that references to thread-locals can't "escape" from doesn't suffer from this issue.