I've been using lazy_static until now but was trying to migrate to OnceCell.
It seems I can't have a OnceCell<Regex> even though I'm never calling mutable methods on the regex. I'm not even using multiple threads.
The error I'm getting is:
Any way to solve this or do I have to use OnceLock / continue using lazy_static? I don't want to add the overhead of using OnceLock if not necessary.
So, the error you're getting here is that the static itself needs synchronization, multiple threads can try to initialize the OnceCell at once because this is a static (only one initialization function gets called, the rest have to block).
consider an example like this:
use regex::Regex;
use std::cell::OnceCell;
// this can't compile because of the code below.
static INSTANCE: OnceCell<Regex> = OnceCell::new();
fn main() {
std::thread::spawn(|| {
let foo = INSTANCE.get_or_init(|| Regex::new("abc"));
// something that does something with `foo`
});
// uh-oh, this can happen at the exact same time as the above,
// which is UB if this compiles (we have a data race).
let foo = INSTANCE.get_or_init(|| Regex::new("abc"));
// again something that does something with foo.
}
lazy_static uses a lock under the hood. Specifically, when std is available (when std isn't available I think it uses a spinlock but I didn't actually check) it uses https://doc.rust-lang.org/std/sync/struct.Once.html (not to be confused with OnceCell/OnceLock), which explicitly states it locks. As in the excerpt below:
This method will block the calling thread if another initialization routine is currently running.
The short answer is, yes, you have to use OnceLock if you want this in a static, and OnceLock does roughly what lazy_static does anyway.
First of all, thank you for this very elaborate answer. So that means OnceCell can never be used in a static but only as local variable / member variable? I see, that makes sense and also reduces its use cases by a lot of what I initially thought. But your example makes sense and the name/package also suggest it's not thread-safe in itself, I had just wrongly assumed OnceCell was meant to be used in static contexts.
Yeah, it doesn't help that in the once_cell crate, the thread-safe version was also called OnceCell; you had to choose between once_cell::sync::OnceCell and once_cell::unsync::OnceCell. But when it was added to the standard library, once_cell::sync::OnceCell was renamed to OnceLock to make them easier distinguishable. So