How to design a generic thread local

I have codes like this:

use std::cell::UnsafeCell;

struct Foo<T: 'static> {
    t: T
}

impl<T: 'static> Foo<T> {
    thread_local! {
        static TL: UnsafeCell<Vec<T>> = UnsafeCell::new(Vec::new());
    }
}

fn main() {}

And the code will not compile because I use of generic parameter from outer function. What I don't understand is the hint:

error[E0401]: can't use generic parameters from outer function
 --> src/main.rs:7:35
  |
5 | impl<T> Foo<T> {
  |      - type parameter from outer function
6 |     thread_local! {
7 |         static TL: UnsafeCell<Vec<T>> = UnsafeCell::new(Vec::new());
  |                                   ^ use of generic parameter from outer function
  |
  = help: try using a local generic parameter instead

What is a "local generic parameter"? And how do you design thread locals like this(thread local with generic)?

P.S. the compiler will panic if you run the code on stable 1.63.0, I've already create a ticket

P.P.S I added static bounds.

You can't. The thread-local variable is backed by a static, which can't be generic (yet).

Is there any unstable feature to do that?

The error message probably makes more sense for the result of expanding the macro. (I'm in mobile right now, so please excuse I'm not verifying this myself.) I assume it might involve a function or struct or so [1], defined inside of the method you're in. "Local" generic parameter would then refer to a generic parameter of that inner function or struct.


  1. or maybe the static itself creates such error messages, too? Anyways it's going to be some form of nested item ↩ī¸Ž

2 Likes

AFAICT no, the issue is not a lack of implementation. The technical problem is that linking can't guarantee uniqueness of statics when trying to merge multiple instantiations of a generic with identical sets of type parameters.

2 Likes

To help solve your original problem, you could use something like a thread-local HashMap<TypeId, Box<dyn Any>>.

pub struct ThreadLocal<T: 'static>(fn() -> T);

thread_local! {
    static REGISTRY: RefCell<HashMap<TypeId, Box<dyn Any>>> = RefCell::new(HashMap::new());
}

impl<T: 'static> ThreadLocal<T> {
    pub const fn new(initializer: fn() -> T) -> Self {
        ThreadLocal(initializer)
    }

    pub fn with<R>(&self, thunk: impl FnOnce(&T) -> R) -> R {
        REGISTRY.with(|registry| {
            let id = TypeId::of::<T>();
            let mut registry = registry.borrow_mut();

            if !registry.contains_key(&id) {
                let initial_value = (self.0)();
                registry.insert(id, Box::new(initial_value));
            }

            thunk(
                registry[&id]
                    .downcast_ref::<T>()
                    .expect("Guaranteed by the initializer"),
            )
        })
    }
}

You could then use it like this:

fn main() {
    static STRING: ThreadLocal<String> = ThreadLocal::new(|| "Hello, World!".to_string());
    static COUNT: ThreadLocal<usize> = ThreadLocal::new(|| 4);

    let count = COUNT.with(|&c| c);
    STRING.with(|s| {
        println!("{}", s.repeat(count));
    });
}

(playground)

I was originally going to use STRING.with(|s| COUNT.with(|c| println!("{}", s.repeat(c))) as the example but ran into a panic because COUNT.with() was calling registry.borrow_mut() while STRINGS.with() had a live registry.borrow().

You should be able to solve that with more RefCells, though, so I'll leave re-entrancy as an exercise for the reader.

2 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.