Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Try trait to make Once[Cell | Lock]::get_or_try_init generic over return type #107122

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 29 additions & 13 deletions library/core/src/cell/once.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::cell::UnsafeCell;
use crate::ops::{ControlFlow, FromResidual, Residual, Try};
use crate::{fmt, mem};

/// A cell which can nominally be written to only once.
Expand Down Expand Up @@ -228,14 +229,18 @@ impl<T> OnceCell<T> {
/// assert_eq!(cell.get(), Some(&92))
/// ```
#[unstable(feature = "once_cell_try", issue = "109737")]
pub fn get_or_try_init<F, E>(&self, f: F) -> Result<&T, E>
pub fn get_or_try_init<'a, F, R>(&'a self, f: F) -> <R::Residual as Residual<&'a T>>::TryType
where
F: FnOnce() -> Result<T, E>,
F: FnOnce() -> R,
R: Try<Output = T, Residual: Residual<&'a T>>,
{
if let Some(val) = self.get() {
return Ok(val);
if self.get().is_none() {
if let ControlFlow::Break(residual) = self.try_init(f) {
return FromResidual::from_residual(residual);
}
}
self.try_init(f)

try { self.get().unwrap() }
}

/// Gets the mutable reference of the contents of the cell, initializing
Expand Down Expand Up @@ -266,29 +271,40 @@ impl<T> OnceCell<T> {
/// assert_eq!(cell.get(), Some(&1236))
/// ```
#[unstable(feature = "once_cell_get_mut", issue = "121641")]
pub fn get_mut_or_try_init<F, E>(&mut self, f: F) -> Result<&mut T, E>
pub fn get_mut_or_try_init<'a, F, R>(
&'a mut self,
f: F,
) -> <R::Residual as Residual<&'a mut T>>::TryType
where
F: FnOnce() -> Result<T, E>,
F: FnOnce() -> R,
R: Try<Output = T, Residual: Residual<&'a mut T>>,
{
if self.get().is_none() {
self.try_init(f)?;
if let ControlFlow::Break(residual) = self.try_init(f) {
return FromResidual::from_residual(residual);
}
}
Ok(self.get_mut().unwrap())

try { self.get_mut().unwrap() }
}

// Avoid inlining the initialization closure into the common path that fetches
// the already initialized value
#[cold]
fn try_init<F, E>(&self, f: F) -> Result<&T, E>
fn try_init<F, R>(&self, f: F) -> ControlFlow<R::Residual, ()>
where
F: FnOnce() -> Result<T, E>,
F: FnOnce() -> R,
R: Try<Output = T>,
{
let val = f()?;
let val = f().branch()?;
// Note that *some* forms of reentrant initialization might lead to
// UB (see `reentrant_init` test). I believe that just removing this
// `panic`, while keeping `try_insert` would be sound, but it seems
// better to panic, rather than to silently use an old value.
if let Ok(val) = self.try_insert(val) { Ok(val) } else { panic!("reentrant init") }
match self.try_insert(val) {
Ok(_) => ControlFlow::Continue(()),
Err(_) => panic!("reentrant init"),
}
}

/// Consumes the cell, returning the wrapped value.
Expand Down
2 changes: 2 additions & 0 deletions library/std/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,8 @@
#![feature(str_internals)]
#![feature(strict_provenance)]
#![feature(strict_provenance_atomic_ptr)]
#![feature(try_trait_v2)]
#![feature(try_trait_v2_residual)]
#![feature(ub_checks)]
// tidy-alphabetical-end
//
Expand Down
56 changes: 31 additions & 25 deletions library/std/src/sync/once_lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::cell::UnsafeCell;
use crate::fmt;
use crate::marker::PhantomData;
use crate::mem::MaybeUninit;
use crate::ops::{ControlFlow, FromResidual, Residual, Try};
use crate::panic::{RefUnwindSafe, UnwindSafe};
use crate::sync::Once;

Expand Down Expand Up @@ -375,24 +376,23 @@ impl<T> OnceLock<T> {
/// ```
#[inline]
#[unstable(feature = "once_cell_try", issue = "109737")]
pub fn get_or_try_init<F, E>(&self, f: F) -> Result<&T, E>
pub fn get_or_try_init<'a, F, R>(&'a self, f: F) -> <R::Residual as Residual<&'a T>>::TryType
where
F: FnOnce() -> Result<T, E>,
F: FnOnce() -> R,
R: Try<Output = T, Residual: Residual<&'a T>>,
{
// Fast path check
// NOTE: We need to perform an acquire on the state in this method
// in order to correctly synchronize `LazyLock::force`. This is
// currently done by calling `self.get()`, which in turn calls
// `self.is_initialized()`, which in turn performs the acquire.
if let Some(value) = self.get() {
return Ok(value);
// currently done by calling `self.is_initialized()`.
if !self.is_initialized() {
if let ControlFlow::Break(residual) = self.initialize(f) {
return FromResidual::from_residual(residual);
}
}
self.initialize(f)?;

debug_assert!(self.is_initialized());

// SAFETY: The inner value has been initialized
Ok(unsafe { self.get_unchecked() })
try { unsafe { self.get_unchecked() } }
}

/// Gets the mutable reference of the contents of the cell, initializing
Expand Down Expand Up @@ -426,16 +426,22 @@ impl<T> OnceLock<T> {
/// ```
#[inline]
#[unstable(feature = "once_cell_get_mut", issue = "121641")]
pub fn get_mut_or_try_init<F, E>(&mut self, f: F) -> Result<&mut T, E>
pub fn get_mut_or_try_init<'a, F, R>(
&'a mut self,
f: F,
) -> <R::Residual as Residual<&'a mut T>>::TryType
where
F: FnOnce() -> Result<T, E>,
F: FnOnce() -> R,
R: Try<Output = T, Residual: Residual<&'a mut T>>,
{
if self.get().is_none() {
self.initialize(f)?;
if !self.is_initialized() {
if let ControlFlow::Break(residual) = self.initialize(f) {
return FromResidual::from_residual(residual);
}
}
debug_assert!(self.is_initialized());

// SAFETY: The inner value has been initialized
Ok(unsafe { self.get_unchecked_mut() })
try { unsafe { self.get_unchecked_mut() } }
}

/// Consumes the `OnceLock`, returning the wrapped value. Returns
Expand Down Expand Up @@ -499,22 +505,22 @@ impl<T> OnceLock<T> {

#[cold]
#[optimize(size)]
fn initialize<F, E>(&self, f: F) -> Result<(), E>
fn initialize<F, R>(&self, f: F) -> ControlFlow<R::Residual, ()>
where
F: FnOnce() -> Result<T, E>,
F: FnOnce() -> R,
R: Try<Output = T>,
{
let mut res: Result<(), E> = Ok(());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might make sense to check for other uses of res in the relevant files, as that’s the name I use a lot.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is one other case in Clone, but I think it's fine as is. I just don't want to use res here since it could cause confusion as to whether it means "result" or "residual".

let slot = &self.value;
let mut res = ControlFlow::Continue(());

// Ignore poisoning from other threads
// If another thread panics, then we'll be able to run our closure
self.once.call_once_force(|p| {
match f() {
Ok(value) => {
unsafe { (&mut *slot.get()).write(value) };
match f().branch() {
ControlFlow::Continue(value) => {
unsafe { (&mut *self.value.get()).write(value) };
}
Err(e) => {
res = Err(e);
ControlFlow::Break(residual) => {
res = ControlFlow::Break(residual);

// Treat the underlying `Once` as poisoned since we
// failed to initialize our value.
Expand Down
8 changes: 6 additions & 2 deletions library/std/src/sync/once_lock/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,18 +83,22 @@ fn clone() {
#[test]
#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
fn get_or_try_init() {
let cell: OnceLock<String> = OnceLock::new();
let mut cell: OnceLock<String> = OnceLock::new();
assert!(cell.get().is_none());

let res = panic::catch_unwind(|| cell.get_or_try_init(|| -> Result<_, ()> { panic!() }));
assert!(res.is_err());
assert!(!cell.is_initialized());
assert!(cell.get().is_none());

assert_eq!(cell.get_or_try_init(|| None), None);
assert_eq!(cell.get_or_try_init(|| Err(())), Err(()));

assert_eq!(cell.get_or_try_init(|| Ok::<_, ()>("hello".to_string())), Ok(&"hello".to_string()));
assert_eq!(cell.get(), Some(&"hello".to_string()));
assert_eq!(cell.take(), Some("hello".to_string()));

assert_eq!(cell.get_or_try_init(|| Some("42".to_string())), Some(&"42".to_string()));
assert_eq!(cell.get(), Some(&"42".to_string()));
}

#[test]
Expand Down
Loading