A function is getting called from two completely different places and I want to run that function only once in a threaded safe environment. Consider this scenario:
use std::sync::{Once, Mutex};
use lazy_static::lazy_static;
lazy_static! {
static ref INIT: Once = Once::new();
static ref VALUE: Mutex<Option<bool>> = Mutex::new(None);
}
fn expensive_function() -> bool {
println!("Expensive computation happening...");
false
}
fn get_value() -> bool {
INIT.call_once(|| {
*VALUE.lock().unwrap() = Some(expensive_function());
});
VALUE.lock().unwrap().unwrap()
}
fn main() {
// First call - will run expensive_function
println!("First call: {}", get_value());
// Second call - will return cached value
println!("Second call: {}", get_value());
}
This works and gives the ouput as expected and is using the mutexes to take care of threaded environment.
Expensive computation happening... First call: false Second call: false
However, there are certain cases which I am not able to think and need help from the experts. 1. What if the thread is poisned and 2. So many unwraps() are not good I believe.
What are the suggestions from the community here? The second call could be in other files as well.
I have tried making taking care of poisened thread explicitly and I could not make it work. Also tried with match statements to avoid unwraps and got confused.
As mentioned in the comments, using std::sync::LazyLock
allows you to get rid of both Option
and Mutex
, eliminating both unwraps:
fn get_value() -> bool {
static VALUE: LazyLock<bool> = LazyLock::new(|| expensive_function());
*VALUE
}
If you need to pass arguments to expensive_function()
, use OnceLock
instead:
fn get_value() -> bool {
static VALUE: OnceLock<bool> = OnceLock::new();
// here you can pass arbitrary data to expensive_function()
*VALUE.get_or_init(|| expensive_function())
}
If you actually do need to occasionally modify the data, then you'll still need the Mutex
and its unwrap due to poisoning. This unwrap is something you learn to accept as a pattern - after all, if the mutex is really poisoned (the thread that held it panicked), then you probably want to propagate the panic to the threads that attempt to use it. And if you still find the mutex-related unwrap()
unpalatable, you can switch from std::sync::Mutex
to parking_lot::Mutex
, which doesn't have the poisoning feature.