Accessng thread local varialbes from generic functions

I have a case where I need a thread local variable for each of a set of (floating point type, unsigned integer type). Currently, for f64 and u32, I call it THIS_THREAD_TAPE_F64_U32.

I cannot figure out how to write a generic function, e.g. my_fun<F, U>, that can access the tape corresponding to the floating point type F and the unsigned integer type U ?

You need a trait in order to dispatch on the types to the individual thread-locals.

use std::thread::LocalKey;

#[derive(Default)]
struct Tape<F, U> {
    _whatever: std::marker::PhantomData<(F, U)>,
}

trait TlTape<U: 'static>: Sized + 'static {
    fn get() -> LocalKey<Tape<Self, U>>;
}

impl TlTape<u32> for f64 {
    fn get() -> LocalKey<Tape<f64, u32>> {
        std::thread_local! {
            static THIS: Tape<f64, u32> = Tape::default();
        }
        THIS
    }
}

fn my_fun<F, U>()
where
    F: TlTape<U>,
    U: 'static
{
    let tape_local = <F as TlTape<U>>::get();
}

If you want, you can write a macro to generate each impl without writing duplicate code.

2 Likes

I think this is the right idea, but I have not quite been able to make it work yet. Here is a main.rs that demonstrates the problem I am having:

use std::thread::LocalKey;
use std::cell::RefCell;

// Tape
#[derive(Default, Debug)]
struct Tape<F, U> {
    value : Vec<F> ,
    index : Vec<U> ,
}

// ThisThreadTape
trait ThisThreadTape<U: 'static>: Sized + 'static {
    fn my_get() -> LocalKey< RefCell< Tape<Self, U> > >;
}

impl ThisThreadTape<u32> for f64 {
    fn my_get() -> LocalKey< RefCell< Tape<f64, u32> > > {
        std::thread_local! {
            static THIS: RefCell< Tape<f64, u32> > = RefCell::new( Tape::default() );
        }
        THIS
    }
}

fn get_this_thread_tape<F, U>() -> LocalKey< RefCell< Tape<F, U> > >
where
    F: ThisThreadTape<U>,
    U: 'static
{
    <F as ThisThreadTape<U>>::my_get()
}

fn main() {
   let localkey : LocalKey< RefCell< Tape<f64, u32> > > = get_this_thread_tape();
   localkey.with_borrow( |tape| {
      print!( "tape.value = {}", tape.value.len()); 
      print!( "tape.index = {}", tape.index.len()); 
   } );
}

The response I get to cargo run is:

   Compiling cargo_test v0.1.0 (/Users/bradbell/trash/rust/cargo_test)
error[E0597]: `localkey` does not live long enough
  --> src/main.rs:36:4
   |
35 |      let localkey : LocalKey< RefCell< Tape<f64, u32> > > = get_this_thr...
   |          -------- binding `localkey` declared here
36 |      localkey.with_borrow( |tape| {
   |      -^^^^^^^
   |      |
   |  ____borrowed value does not live long enough
   | |
37 | |       print!( "tape.value = {}", tape.value.len()); 
38 | |       print!( "tape.index = {}", tape.index.len()); 
39 | |    } );
   | |______- argument requires that `localkey` is borrowed for `'static`
40 |   }
   |   - `localkey` dropped here while still borrowed

For more information about this error, try `rustc --explain E0597`.

It compiles and runs for me if I change the get functions to return &'static LocalKey<...> instead of LocalKey<...>.

That works and makes sense; Thanks. Here is the difference I have from the main.rs above to the working version:

<     fn my_get() -> LocalKey< RefCell< Tape<Self, U> > >;
---
>     fn my_get() -> &'static LocalKey< RefCell< Tape<Self, U> > >;
17c17
<     fn my_get() -> LocalKey< RefCell< Tape<f64, u32> > > {
---
>     fn my_get() -> &'static LocalKey< RefCell< Tape<f64, u32> > > {
21c21
<         THIS
---
>         &THIS
25c25
< fn get_this_thread_tape<F, U>() -> LocalKey< RefCell< Tape<F, U> > >
---
> fn get_this_thread_tape<F, U>() -> &'static LocalKey< RefCell< Tape<F, U> > >
34c34
<    let localkey : LocalKey< RefCell< Tape<f64, u32> > > = get_this_thread_tape();
---
>    let localkey : &LocalKey< RefCell< Tape<f64, u32> > > = get_this_thread_tape();
1 Like

The following code

trait TlTape<U: 'static>: Sized + 'static {
    fn get() -> LocalKey<Tape<Self, U>>;
}

Uses the Sized trait. This in turn results in the trait not being dyn compatible; see

The following text appears in the documentation for dyn compatibility:
'This concept was formerly known as object safety.'

Is there some sense in which using the Sized trait is not safe ?

No, that's the reason for the change, as I understand it. To be more clear (mentions dyn) and not refer to safety. It means that the trait can't be implemented for dyn T, since the Self type has to be Sized. A dyn T type gets an automatic implementation of its own trait, but that's not possible in this case. If you only need Sized for certain methods, you can add where Self: Sized to just them instead.

3 Likes

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.