Suppose I want to have exactly one instance of my struct for each thread.
Instances should be created lazily.
Is it possible to manage those instances with thread_local!and return a &'static reference?
I can't seem to get the lifetimes right in function thread_instance():
thread_local! {
static INSTANCE: MyStruct = MyStruct::new();
}
pub struct MyStruct {
/* ... */
}
impl MyStruct {
/// Return instance for current thread
/// Should be: Created "on demand" when accessed for the first time
pub fn thread_instance() -> &'static Self {
INSTANCE.with( /* ... */ ) // <-- what to do here ???
}
pub fn new() -> Self {
/* ... */
}
}
Each threat will have one "static" instance, so getting a &'static ref should be possible?
I think we could use Rc<T> here, but it kind of contradicts using thread_local! to create a separate "static" instance for each thread. Is there a better solution for this kind problem ???
Use Rc<T> or provide an API that accepts a callback, anything else is unsound.
rand crate in the past provided a reference to an user wrapped in structure that is !Send + !Sync, but they moved to Rc<T> when it was pointed out it was unsound in presence of other thread locals with destructors (https://github.com/rust-random/rand/issues/968).
The very point of the callback-based API of LocalKey is that you can't return a reference to outside the function. The reason for this is simple: thread locals aren't actuallystatic. They live only as long as the enclosing thread, and no longer. If you could return a &'static reference to a thread-local, then you could do this:
let handle = std::thread::spawn(|| {
get_static_ref_to_thread_local()
});
let invalid_ref = handle.join().unwrap();
which would cause a dangling reference.
You can observe how it segfaults when unsafely forced: Playground.
Then what is the standard use-case for thread_local!? I mean, if we ca not return the "thread_local" struct as a reference. And we also do notwant to clone() it – because, having exactly one instance per thread is all the reason why we wanted to use thread_local!.
So, wrapping the value in an Rc<T> and returning a clone of the Rc<T> is the way to go, right?
Another question: How to deal with constructors that might fail?
Just use the thread-local directly - every time you need to do something with it you can run INSTANCE.with(...) to access its state.
There are ways of hiding this using indirection (i.e. objects/functions that hide the with() calls), but at the end of the day, there (by design!) is no way to move ownership of a thread-local variable out of the ThreadHandle or return a reference to its value.
I normally interpret this friction as my code saying I've got a weird dependency that behaves differently to other values and therefore needs to be handled specially. Thread-locals can make things really tricky to debug or test, so it's worth asking whether you need them or how you can best encapsulate the thread-local-ness.
It's for when you want exactly one instance of the thing per thread (or when you really wanted a real static/global, but you can't have one, because the type you are trying to instantiate isn't thread-safe, which Rust requires in statics).
Returning a direct, long-lived reference to a value is not the only way you can use it. The with() API of LocalKey is still useful without Rc. You should first and foremost refactor your code in a way that it's possible to compute any other results you need without a 'static reference, directly from inside the callback passed to with().
You don't need to re-invent lazy initialization, since thread_local! is already lazy by itself. Here's a much simpler equivalent of your snippet above.
You don't need to re-invent lazy initialization, since thread_local! is already lazy by itself. Here's a much simpler equivalent of your snippet above.
Thanks! But if I get this right, then your example will store the Result in the thread_local! on the very first attempt, regardless of whether it succeeded or failed. And then that's it.
What I had in mind (and tried to implement) is that if the initialization failed, then MyStruct::instance() will return the error, but the thread_local! remains in "uninitialized" state in that case, so that we may retry the initialization when MyStruct::instance() is called again...