mirror of
https://github.com/rust-lang/rust.git
synced 2025-05-14 02:49:40 +00:00
Rollup merge of #125696 - workingjubilee:please-dont-say-you-are-lazy, r=Nilstrieb
Explain differences between `{Once,Lazy}{Cell,Lock}` types 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 `LazyCell` and `LazyLock` types. The answer for the `Lazy*` 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 every `get_or_init` site. For everything else there's the `Once*` 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 `OnceLock` can be useful, despite `LazyLock` existing: you can do some cool stuff with it that `LazyLock` simply can't support due to its mere definition. The pair of `std::sync::*Lock`s are usable inside a `static`, and can serve roles in async or multithreaded (or asynchronously multithreaded) programs that `*Cell`s cannot. Because of this, they received most of my attention. Fixes #124696 Fixes #125615
This commit is contained in:
commit
ee04e0f35e
@ -82,6 +82,20 @@
|
||||
//!
|
||||
//! The corresponding [`Sync`] version of `OnceCell<T>` is [`OnceLock<T>`].
|
||||
//!
|
||||
//! ## `LazyCell<T, F>`
|
||||
//!
|
||||
//! A common pattern with OnceCell is, for a given OnceCell, to use the same function on every
|
||||
//! call to [`OnceCell::get_or_init`] with that cell. This is what is offered by [`LazyCell`],
|
||||
//! which pairs cells of `T` with functions of `F`, and always calls `F` before it yields `&T`.
|
||||
//! This happens implicitly by simply attempting to dereference the LazyCell to get its contents,
|
||||
//! so its use is much more transparent with a place which has been initialized by a constant.
|
||||
//!
|
||||
//! More complicated patterns that don't fit this description can be built on `OnceCell<T>` instead.
|
||||
//!
|
||||
//! `LazyCell` works by providing an implementation of `impl Deref` that calls the function,
|
||||
//! so you can just use it by dereference (e.g. `*lazy_cell` or `lazy_cell.deref()`).
|
||||
//!
|
||||
//! The corresponding [`Sync`] version of `LazyCell<T, F>` is [`LazyLock<T, F>`].
|
||||
//!
|
||||
//! # When to choose interior mutability
|
||||
//!
|
||||
@ -230,6 +244,7 @@
|
||||
//! [`RwLock<T>`]: ../../std/sync/struct.RwLock.html
|
||||
//! [`Mutex<T>`]: ../../std/sync/struct.Mutex.html
|
||||
//! [`OnceLock<T>`]: ../../std/sync/struct.OnceLock.html
|
||||
//! [`LazyLock<T, F>`]: ../../std/sync/struct.LazyLock.html
|
||||
//! [`Sync`]: ../../std/marker/trait.Sync.html
|
||||
//! [`atomic`]: crate::sync::atomic
|
||||
|
||||
|
@ -29,34 +29,26 @@ union Data<T, F> {
|
||||
/// # Examples
|
||||
///
|
||||
/// Initialize static variables with `LazyLock`.
|
||||
///
|
||||
/// ```
|
||||
/// use std::collections::HashMap;
|
||||
///
|
||||
/// use std::sync::LazyLock;
|
||||
///
|
||||
/// static HASHMAP: LazyLock<HashMap<i32, String>> = LazyLock::new(|| {
|
||||
/// println!("initializing");
|
||||
/// let mut m = HashMap::new();
|
||||
/// m.insert(13, "Spica".to_string());
|
||||
/// m.insert(74, "Hoyten".to_string());
|
||||
/// m
|
||||
/// // n.b. static items do not call [`Drop`] on program termination, so this won't be deallocated.
|
||||
/// // this is fine, as the OS can deallocate the terminated program faster than we can free memory
|
||||
/// // but tools like valgrind might report "memory leaks" as it isn't obvious this is intentional.
|
||||
/// static DEEP_THOUGHT: LazyLock<String> = LazyLock::new(|| {
|
||||
/// # mod another_crate {
|
||||
/// # pub fn great_question() -> String { "42".to_string() }
|
||||
/// # }
|
||||
/// // M3 Ultra takes about 16 million years in --release config
|
||||
/// another_crate::great_question()
|
||||
/// });
|
||||
///
|
||||
/// fn main() {
|
||||
/// println!("ready");
|
||||
/// std::thread::spawn(|| {
|
||||
/// println!("{:?}", HASHMAP.get(&13));
|
||||
/// }).join().unwrap();
|
||||
/// println!("{:?}", HASHMAP.get(&74));
|
||||
///
|
||||
/// // Prints:
|
||||
/// // ready
|
||||
/// // initializing
|
||||
/// // Some("Spica")
|
||||
/// // Some("Hoyten")
|
||||
/// }
|
||||
/// // The `String` is built, stored in the `LazyLock`, and returned as `&String`.
|
||||
/// let _ = &*DEEP_THOUGHT;
|
||||
/// // The `String` is retrieved from the `LazyLock` and returned as `&String`.
|
||||
/// let _ = &*DEEP_THOUGHT;
|
||||
/// ```
|
||||
///
|
||||
/// Initialize fields with `LazyLock`.
|
||||
/// ```
|
||||
/// use std::sync::LazyLock;
|
||||
|
@ -136,7 +136,10 @@
|
||||
//! - [`Once`]: Used for a thread-safe, one-time global initialization routine
|
||||
//!
|
||||
//! - [`OnceLock`]: Used for thread-safe, one-time initialization of a
|
||||
//! global variable.
|
||||
//! variable, with potentially different initializers based on the caller.
|
||||
//!
|
||||
//! - [`LazyLock`]: Used for thread-safe, one-time initialization of a
|
||||
//! variable, using one nullary initializer function provided at creation.
|
||||
//!
|
||||
//! - [`RwLock`]: Provides a mutual exclusion mechanism which allows
|
||||
//! multiple readers at the same time, while allowing only one
|
||||
|
@ -8,47 +8,17 @@ use crate::sync::Once;
|
||||
/// A synchronization primitive which can nominally be written to only once.
|
||||
///
|
||||
/// This type is a thread-safe [`OnceCell`], and can be used in statics.
|
||||
/// 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(|| ...)`].
|
||||
///
|
||||
/// [`OnceCell`]: crate::cell::OnceCell
|
||||
/// [`LazyLock<T, F>`]: crate::sync::LazyLock
|
||||
/// [`LazyLock::new(|| ...)`]: crate::sync::LazyLock::new
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Using `OnceLock` to store a function’s previously computed value (a.k.a.
|
||||
/// ‘lazy static’ or ‘memoizing’):
|
||||
///
|
||||
/// ```
|
||||
/// use std::sync::OnceLock;
|
||||
///
|
||||
/// struct DeepThought {
|
||||
/// answer: String,
|
||||
/// }
|
||||
///
|
||||
/// impl DeepThought {
|
||||
/// # fn great_question() -> String {
|
||||
/// # "42".to_string()
|
||||
/// # }
|
||||
/// #
|
||||
/// fn new() -> Self {
|
||||
/// Self {
|
||||
/// // M3 Ultra takes about 16 million years in --release config
|
||||
/// answer: Self::great_question(),
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// fn computation() -> &'static DeepThought {
|
||||
/// // n.b. static items do not call [`Drop`] on program termination, so if
|
||||
/// // [`DeepThought`] impls Drop, that will not be used for this instance.
|
||||
/// static COMPUTATION: OnceLock<DeepThought> = OnceLock::new();
|
||||
/// COMPUTATION.get_or_init(|| DeepThought::new())
|
||||
/// }
|
||||
///
|
||||
/// // The `DeepThought` is built, stored in the `OnceLock`, and returned.
|
||||
/// let _ = computation().answer;
|
||||
/// // The `DeepThought` is retrieved from the `OnceLock` and returned.
|
||||
/// let _ = computation().answer;
|
||||
/// ```
|
||||
///
|
||||
/// Writing to a `OnceLock` from a separate thread:
|
||||
///
|
||||
/// ```
|
||||
@ -73,6 +43,55 @@ use crate::sync::Once;
|
||||
/// Some(&12345),
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// You can use `OnceLock` to implement a type that requires "append-only" logic:
|
||||
///
|
||||
/// ```
|
||||
/// use std::sync::{OnceLock, atomic::{AtomicU32, Ordering}};
|
||||
/// use std::thread;
|
||||
///
|
||||
/// struct OnceList<T> {
|
||||
/// data: OnceLock<T>,
|
||||
/// next: OnceLock<Box<OnceList<T>>>,
|
||||
/// }
|
||||
/// impl<T> OnceList<T> {
|
||||
/// const fn new() -> OnceList<T> {
|
||||
/// OnceList { data: OnceLock::new(), next: OnceLock::new() }
|
||||
/// }
|
||||
/// fn push(&self, value: T) {
|
||||
/// // FIXME: this impl is concise, but is also slow for long lists or many threads.
|
||||
/// // as an exercise, consider how you might improve on it while preserving the behavior
|
||||
/// if let Err(value) = self.data.set(value) {
|
||||
/// let next = self.next.get_or_init(|| Box::new(OnceList::new()));
|
||||
/// next.push(value)
|
||||
/// };
|
||||
/// }
|
||||
/// fn contains(&self, example: &T) -> bool
|
||||
/// where
|
||||
/// T: PartialEq,
|
||||
/// {
|
||||
/// self.data.get().map(|item| item == example).filter(|v| *v).unwrap_or_else(|| {
|
||||
/// self.next.get().map(|next| next.contains(example)).unwrap_or(false)
|
||||
/// })
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // Let's exercise this new Sync append-only list by doing a little counting
|
||||
/// static LIST: OnceList<u32> = OnceList::new();
|
||||
/// static COUNTER: AtomicU32 = AtomicU32::new(0);
|
||||
///
|
||||
/// let vec = (0..thread::available_parallelism().unwrap().get()).map(|_| thread::spawn(|| {
|
||||
/// while let i @ 0..=1000 = COUNTER.fetch_add(1, Ordering::Relaxed) {
|
||||
/// LIST.push(i);
|
||||
/// }
|
||||
/// })).collect::<Vec<thread::JoinHandle<_>>>();
|
||||
/// vec.into_iter().for_each(|handle| handle.join().unwrap());
|
||||
///
|
||||
/// for i in 0..=1000 {
|
||||
/// assert!(LIST.contains(&i));
|
||||
/// }
|
||||
///
|
||||
/// ```
|
||||
#[stable(feature = "once_cell", since = "1.70.0")]
|
||||
pub struct OnceLock<T> {
|
||||
once: Once,
|
||||
|
Loading…
Reference in New Issue
Block a user