Skip to content

Modernizing LazyStatic APIs #111

Open
@matklad

Description

@matklad

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 from once_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)*) => { ... }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions