Rollup merge of #144872 - connortsui20:once-poison-docs, r=Amanieu

Document Poisoning in `LazyCell` and `LazyLock`

Currently, there is no documentation of poisoning behavior in either `LazyCell` or `LazyLock`, even though both of them can be observed as poisoned by users.

`LazyCell` [plagyround example](https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=9cf38b8dc56db100848f54085c2c697d)

`LazyLock` [playground example](https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=f1cd6f9fe16636e347ebb695a0ce30c0)

# Open Questions

- [x] Is it worth making the implementation of `LazyLock` more complicated to ensure that the the panic message is `"LazyLock instance has previously been poisoned"` instead of `"Once instance has previously been poisoned"`? See the `LazyLock` playground link above for more context.
- [x] Does it make sense to move `LazyLock` into the `poison` module? It is certainly a poison-able type, but at the same time it is slightly different from the 4 other types currently in the `poison` module in that it is unrecoverable. I think this is more of a libs-api question.

``@rustbot`` label +T-libs-api

Please let me know if these open questions deserve a separate issue / PR!
This commit is contained in:
Samuel Tardieu 2025-08-05 03:51:38 +02:00 committed by GitHub
commit 1724af9f1e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 118 additions and 5 deletions

View file

@ -15,6 +15,22 @@ enum State<T, F> {
///
/// [`std::sync::LazyLock`]: ../../std/sync/struct.LazyLock.html
///
/// # Poisoning
///
/// If the initialization closure passed to [`LazyCell::new`] panics, the cell will be poisoned.
/// Once the cell is poisoned, any threads that attempt to access this cell (via a dereference
/// or via an explicit call to [`force()`]) will panic.
///
/// This concept is similar to that of poisoning in the [`std::sync::poison`] module. A key
/// difference, however, is that poisoning in `LazyCell` is _unrecoverable_. All future accesses of
/// the cell from other threads will panic, whereas a type in [`std::sync::poison`] like
/// [`std::sync::poison::Mutex`] allows recovery via [`PoisonError::into_inner()`].
///
/// [`force()`]: LazyCell::force
/// [`std::sync::poison`]: ../../std/sync/poison/index.html
/// [`std::sync::poison::Mutex`]: ../../std/sync/poison/struct.Mutex.html
/// [`PoisonError::into_inner()`]: ../../std/sync/poison/struct.PoisonError.html#method.into_inner
///
/// # Examples
///
/// ```
@ -64,6 +80,10 @@ impl<T, F: FnOnce() -> T> LazyCell<T, F> {
///
/// Returns `Ok(value)` if `Lazy` is initialized and `Err(f)` otherwise.
///
/// # Panics
///
/// Panics if the cell is poisoned.
///
/// # Examples
///
/// ```
@ -93,6 +113,15 @@ impl<T, F: FnOnce() -> T> LazyCell<T, F> {
///
/// This is equivalent to the `Deref` impl, but is explicit.
///
/// # Panics
///
/// If the initialization closure panics (the one that is passed to the [`new()`] method), the
/// panic is propagated to the caller, and the cell becomes poisoned. This will cause all future
/// accesses of the cell (via [`force()`] or a dereference) to panic.
///
/// [`new()`]: LazyCell::new
/// [`force()`]: LazyCell::force
///
/// # Examples
///
/// ```
@ -123,6 +152,15 @@ impl<T, F: FnOnce() -> T> LazyCell<T, F> {
/// Forces the evaluation of this lazy value and returns a mutable reference to
/// the result.
///
/// # Panics
///
/// If the initialization closure panics (the one that is passed to the [`new()`] method), the
/// panic is propagated to the caller, and the cell becomes poisoned. This will cause all future
/// accesses of the cell (via [`force()`] or a dereference) to panic.
///
/// [`new()`]: LazyCell::new
/// [`force()`]: LazyCell::force
///
/// # Examples
///
/// ```
@ -219,7 +257,8 @@ impl<T, F: FnOnce() -> T> LazyCell<T, F> {
}
impl<T, F> LazyCell<T, F> {
/// Returns a mutable reference to the value if initialized, or `None` if not.
/// Returns a mutable reference to the value if initialized. Otherwise (if uninitialized or
/// poisoned), returns `None`.
///
/// # Examples
///
@ -245,7 +284,8 @@ impl<T, F> LazyCell<T, F> {
}
}
/// Returns a reference to the value if initialized, or `None` if not.
/// Returns a reference to the value if initialized. Otherwise (if uninitialized or poisoned),
/// returns `None`.
///
/// # Examples
///
@ -278,6 +318,15 @@ impl<T, F> LazyCell<T, F> {
#[stable(feature = "lazy_cell", since = "1.80.0")]
impl<T, F: FnOnce() -> T> Deref for LazyCell<T, F> {
type Target = T;
/// # Panics
///
/// If the initialization closure panics (the one that is passed to the [`new()`] method), the
/// panic is propagated to the caller, and the cell becomes poisoned. This will cause all future
/// accesses of the cell (via [`force()`] or a dereference) to panic.
///
/// [`new()`]: LazyCell::new
/// [`force()`]: LazyCell::force
#[inline]
fn deref(&self) -> &T {
LazyCell::force(self)
@ -286,6 +335,14 @@ impl<T, F: FnOnce() -> T> Deref for LazyCell<T, F> {
#[stable(feature = "lazy_deref_mut", since = "1.89.0")]
impl<T, F: FnOnce() -> T> DerefMut for LazyCell<T, F> {
/// # Panics
///
/// If the initialization closure panics (the one that is passed to the [`new()`] method), the
/// panic is propagated to the caller, and the cell becomes poisoned. This will cause all future
/// accesses of the cell (via [`force()`] or a dereference) to panic.
///
/// [`new()`]: LazyCell::new
/// [`force()`]: LazyCell::force
#[inline]
fn deref_mut(&mut self) -> &mut T {
LazyCell::force_mut(self)

View file

@ -25,6 +25,22 @@ union Data<T, F> {
///
/// [`LazyCell`]: crate::cell::LazyCell
///
/// # Poisoning
///
/// If the initialization closure passed to [`LazyLock::new`] panics, the lock will be poisoned.
/// Once the lock is poisoned, any threads that attempt to access this lock (via a dereference
/// or via an explicit call to [`force()`]) will panic.
///
/// This concept is similar to that of poisoning in the [`std::sync::poison`] module. A key
/// difference, however, is that poisoning in `LazyLock` is _unrecoverable_. All future accesses of
/// the lock from other threads will panic, whereas a type in [`std::sync::poison`] like
/// [`std::sync::poison::Mutex`] allows recovery via [`PoisonError::into_inner()`].
///
/// [`force()`]: LazyLock::force
/// [`std::sync::poison`]: crate::sync::poison
/// [`std::sync::poison::Mutex`]: crate::sync::poison::Mutex
/// [`PoisonError::into_inner()`]: crate::sync::poison::PoisonError::into_inner
///
/// # Examples
///
/// Initialize static variables with `LazyLock`.
@ -102,6 +118,10 @@ impl<T, F: FnOnce() -> T> LazyLock<T, F> {
///
/// Returns `Ok(value)` if `Lazy` is initialized and `Err(f)` otherwise.
///
/// # Panics
///
/// Panics if the lock is poisoned.
///
/// # Examples
///
/// ```
@ -136,6 +156,15 @@ impl<T, F: FnOnce() -> T> LazyLock<T, F> {
/// Forces the evaluation of this lazy value and returns a mutable reference to
/// the result.
///
/// # Panics
///
/// If the initialization closure panics (the one that is passed to the [`new()`] method), the
/// panic is propagated to the caller, and the lock becomes poisoned. This will cause all future
/// accesses of the lock (via [`force()`] or a dereference) to panic.
///
/// [`new()`]: LazyLock::new
/// [`force()`]: LazyLock::force
///
/// # Examples
///
/// ```
@ -193,6 +222,15 @@ impl<T, F: FnOnce() -> T> LazyLock<T, F> {
/// This method will block the calling thread if another initialization
/// routine is currently running.
///
/// # Panics
///
/// If the initialization closure panics (the one that is passed to the [`new()`] method), the
/// panic is propagated to the caller, and the lock becomes poisoned. This will cause all future
/// accesses of the lock (via [`force()`] or a dereference) to panic.
///
/// [`new()`]: LazyLock::new
/// [`force()`]: LazyLock::force
///
/// # Examples
///
/// ```
@ -227,7 +265,8 @@ impl<T, F: FnOnce() -> T> LazyLock<T, F> {
}
impl<T, F> LazyLock<T, F> {
/// Returns a mutable reference to the value if initialized, or `None` if not.
/// Returns a mutable reference to the value if initialized. Otherwise (if uninitialized or
/// poisoned), returns `None`.
///
/// # Examples
///
@ -256,7 +295,8 @@ impl<T, F> LazyLock<T, F> {
}
}
/// Returns a reference to the value if initialized, or `None` if not.
/// Returns a reference to the value if initialized. Otherwise (if uninitialized or poisoned),
/// returns `None`.
///
/// # Examples
///
@ -307,6 +347,14 @@ impl<T, F: FnOnce() -> T> Deref for LazyLock<T, F> {
/// This method will block the calling thread if another initialization
/// routine is currently running.
///
/// # Panics
///
/// If the initialization closure panics (the one that is passed to the [`new()`] method), the
/// panic is propagated to the caller, and the lock becomes poisoned. This will cause all future
/// accesses of the lock (via [`force()`] or a dereference) to panic.
///
/// [`new()`]: LazyLock::new
/// [`force()`]: LazyLock::force
#[inline]
fn deref(&self) -> &T {
LazyLock::force(self)
@ -315,6 +363,14 @@ impl<T, F: FnOnce() -> T> Deref for LazyLock<T, F> {
#[stable(feature = "lazy_deref_mut", since = "1.89.0")]
impl<T, F: FnOnce() -> T> DerefMut for LazyLock<T, F> {
/// # Panics
///
/// If the initialization closure panics (the one that is passed to the [`new()`] method), the
/// panic is propagated to the caller, and the lock becomes poisoned. This will cause all future
/// accesses of the lock (via [`force()`] or a dereference) to panic.
///
/// [`new()`]: LazyLock::new
/// [`force()`]: LazyLock::force
#[inline]
fn deref_mut(&mut self) -> &mut T {
LazyLock::force_mut(self)

View file

@ -11,7 +11,7 @@
//!
//! The specifics of how this "poisoned" state affects other threads and whether
//! the panics are recognized reliably or on a best-effort basis depend on the
//! primitive. See [#Overview] below.
//! primitive. See [Overview](#overview) below.
//!
//! For the alternative implementations that do not employ poisoning,
//! see [`std::sync::nonpoison`].