Explain differences between {Once,Lazy}{Cell,Lock} types - #125696
Conversation
|
r? @Nilstrieb rustbot has assigned @Nilstrieb. Use |
|
Technically each of these commits can be landed individually but I feel that it's important to discuss the, well, big picture, when trying to answer "so what's the big picture?", so I'm happy to split this up but that's why I made them as a single PR. |
This comment has been minimized.
This comment has been minimized.
|
what, I thought we stabilized those? |
|
we did...? #37854 (comment) |
a621faf to
f8ddb6d
Compare
|
These tests are executed with the beta compiler ( |
|
ah, I see. |
kaffarell
left a comment
There was a problem hiding this comment.
Left some comments for the text part of the PR. Did not yet look at the example.
| /// In many simple cases, you can use [`LazyLock<T, F>`] instead to get the benefits of this type | ||
| /// with less effort: `LazyLock<T, F>` "looks like" `&T` because it initializes with `F` on deref! | ||
| /// Where OnceLock shines is when LazyLock is too simple to support a given case, as LazyLock | ||
| /// doesn't allow additional inputs to its function after you call [`LazyLock::new(|| ...)`]. |
There was a problem hiding this comment.
This sounds—at least for me—a bit misleading. I would write something like: "doesn't allow different initialization functions after you call ...". What do you think?
There was a problem hiding this comment.
Well, OnceLock (the get_or_init fn) is also nullary, isn't it?
There was a problem hiding this comment.
Any function with N arguments can be curried to a nullary function, but this is not experienced in a profoundly different way than "calling the function". Yes, it is technically a "different function" because you enclose the state, but most people would describe this:
let clos = || f(a, b, c, d, etc);
let value = ONCE_LOCK.get_or_init(clos);as calling f, instead of calling clos, because they actually write this:
let value = ONCE_LOCK.get_or_init(|| f(a, b, c, d, etc));| /// You can use `OnceLock` to implement a type that requires "append-only" logic: | ||
| /// | ||
| /// ``` | ||
| /// #![feature(once_cell_try_insert)] |
There was a problem hiding this comment.
could you write this without the unstable feature? I don't think it's great to have an example using unstable features
There was a problem hiding this comment.
yes, as it turns out!
4d8ec9f to
9ed7cfc
Compare
This example is spiritually an example of LazyLock, as it computes a variable at runtime but accepts no inputs into that process. It is also slightly simpler and thus easier to understand. Change it to an even-more concise version and move it to LazyLock. The example now editorializes slightly more. This may be unnecessary, but it can be educational for the reader.
While slightly verbose, it helps explain "why bother with OnceLock?" This is a point of confusion that has been raised multiple times shortly before and after the stabilization of LazyLock.
|
@bors r+ |
…llaumeGomez Rollup of 11 pull requests Successful merges: - rust-lang#106186 (Add function `core::iter::chain`) - rust-lang#125596 (Convert `proc_macro_back_compat` lint to an unconditional error.) - rust-lang#125696 (Explain differences between `{Once,Lazy}{Cell,Lock}` types) - rust-lang#125917 (Create `run-make` `env_var` and `env_var_os` helpers) - rust-lang#125927 (Ignore `vec_deque_alloc_error::test_shrink_to_unwind` test on non-unwind targets) - rust-lang#125930 (feat(opt-dist): new flag `--benchmark-cargo-config`) - rust-lang#125932 (Fix typo in the docs of `HashMap::raw_entry_mut`) - rust-lang#125933 (Update books) - rust-lang#125944 (Update fuchsia maintainers) - rust-lang#125946 (Include trailing commas in wrapped function declarations [RustDoc]) - rust-lang#125973 (Remove `tests/run-make-fulldeps/pretty-expanded`) Failed merges: - rust-lang#125815 (`rustc_parse` top-level cleanups) r? `@ghost` `@rustbot` modify labels: rollup
The question of "which once-ish cell-ish type should I use?" has been raised multiple times, and is especially important now that we have stabilized the
LazyCellandLazyLocktypes. The answer for theLazy*types is that you would be better off using them if you want to use what is by far the most common pattern: initialize it with a single nullary function that you would call at everyget_or_initsite. For everything else there's theOnce*types."For everything else" is a somewhat weak motivation, as it only describes by negation. While contrasting them is inevitable, I feel positive motivations are more understandable. For this, I now offer a distinct example that helps explain why
OnceLockcan be useful, despiteLazyLockexisting: you can do some cool stuff with it thatLazyLocksimply can't support due to its mere definition.The pair of
std::sync::*Locks are usable inside astatic, and can serve roles in async or multithreaded (or asynchronously multithreaded) programs that*Cells cannot. Because of this, they received most of my attention.Fixes #124696
Fixes #125615