thread_local-rs icon indicating copy to clipboard operation
thread_local-rs copied to clipboard

Remove T: Send as a trait bound on the struct and just rely on method bounds

Open akanalytics opened this issue 1 year ago • 1 comments

I understand that T: Send is a requirement for ThreadLocal to function, but could the Send bound be removed from the struct definition and just retained for method impls.

This would greatly simply generic struct definitions, which nest structs containing a ThreadLocal. Currently the Send bound propagates, even to impls of structs containing thread locals that don't make use of the actual thread local or any of its methods.

Obviously where methods of the ThreadLocal are called then the trait bound is needed.

Use case: I have a struct A<T>, which is contained in struct B<T>, which is contained in struct C<T> etc. If I add a ThreadLocal to A (to count method invocations say or method invocation timing metrics), then B, C and so on, all need to declare T:Send. As do trait impls such as impl Display for C<T>, PAartialEq it seems.

akanalytics avatar Jun 12 '24 11:06 akanalytics

Sure, that sounds fine! Would you like to submit a PR for this?

Amanieu avatar Jun 16 '24 09:06 Amanieu

Trying to understand the model here.

  1. ThreadLocal<T>: Send if and only if T: Send
  2. ThreadLocal<T>: Sync if and only if T: Send, which makes sense since it hands out &T, which is only Send if and only if T: Sync.
  3. ThreadLocal::new and ThreadLocal::with_capacity can be called with any T.
  4. ThreadLocal::{get, get_or, get_or_try, get_or_default} currently all require T: Send but turn a &ThreadLocal<T> into a &T or an Option.
  5. ThreadLocal's Drop impl is only sound if any of the following are true: a. The ThreadLocal is empty. b. T: Send and thus it's safe to drop all instances stored in the ThreadLocal on another thread. c. T: !Send and all of the instances of T stored in the ThreadLocal are all constructed from the thread that is dropping the ThreadLocal.
  6. Rust only allows the Drop impl's generic params to match those of the struct definition.

A ThreadLocal constructed with a T: !Send is, itself, not Send and not Sync, and thus can only be constructed, live, be borrowed on, and dropped from a single thread without unsound unsafe elsewhere. Given points 1, 5a, and 5c, would it not be sound to replace the T: Send constraints on ThreadLocal::get and ThreadLocal::iter with T: Sync? ThreadLocal::iter_mut and the get_or* variants that create would still require T: Send since the items can be moved out of the returned &mut T, including in the Drop impl.

This would resolve the issues with #75 as well.

james7132 avatar Aug 03 '25 09:08 james7132