Description
Context: https://internals.rust-lang.org/t/pre-rfc-lazy-static-move-to-std/7993/36
Rust has progressed enough to make lazy static API less magical. It could look like this (example from once_cell
):
static GLOBAL_DATA: Lazy<Mutex<HashMap<i32, String>>> = sync_lazy! {
let mut m = HashMap::new();
m.insert(13, "Spica".to_string());
m.insert(74, "Hoyten".to_string());
Mutex::new(m)
};
I am creating this issue to discuss what we can do with this exciting possibility :-)
Just to be clear, I am explicitly not suggesting that we should deprecate the current API and switch to the new shiny. There's a ton of code in the wild which uses lazy_static!
, and that is great.
Nevertheless, I think the current API has some problems, and the possible new API has less of them! Specifically, the current lazy_static!
macro is opaque: it has a somewhat unique syntax, which is Rustish, but is not exactly Rust, it creates a unique hidden type behind the scenes and the implementation is hard to follow. I think this are significant drawbacks, especially from the learnability perspective.
When a new rustecean asks "how can I have global data?", the typical answer is: "you need to lazily initialize data on the first access. This is what C++ and Java do at the language level, but Rust this is achieved via the lazy_static
library". And then a rustecan goes to see how lazy_static
is implemented, sees this and thinks "wow, this is almost as horrifying as STL implementations" (well, at least that was my reaction :D).
I'd want to argue that an explicit Lazy<T>
would be clearer at the call site (no more magical unique types) and much easier to understand (you just Ctrl+B/F12/M-./gD and read the impl).
An interesting facet of the new API is that Lazy
does not need to be static!
So, are folks excited about the possibility of getting rid of lazy_static!
macro? I propose the following plan for this:
- we extract
sync::Lazy
fromonce_cell
and publish it as a separate rust-lang-nursery crate,sync_lazy
, with the following API
pub struct Lazy<T, F = fn() -> T> { ... }
impl<T, F: FnOnce() -> T> {
pub /*const*/ fn new(f: F) -> Lazy<T> { ... }
pub fn force(this: &Lazy<T, F>) -> &T { ... }
}
impl <T, F: FnOnce() -> T> Deref for Lazy<T, F> {
Target = T;
fn deref(&self) -> &T { ... }
}
macro_rules! sync_lazy {
($($body:tt)*) => { ... }
}
-
sync_lazy
crate usesparking_lot::Once
. That way, we don't need to wait until non-staticOnce
is stable, and we also giveparking_lot
some more testing, as was requested in Replace synchronization primitives with those from parking_lot rust-lang/rfcs#1632 (hence, cc @Amanieu) -
In the
lazy_static
readme, we point that one might trysync_lazy
instead.