rust-lang / libs-team

The home of the library team
Apache License 2.0
115 stars 18 forks source link

`OnceCell/Lock::try_insert()` #276

Closed daxpedda closed 11 months ago

daxpedda commented 11 months ago

Proposal

Problem statement

I would like to insert a value into a OnceCell/Lock and get back a reference to it while also checking if the value was already set or not.

Motivating examples or use cases

This can be useful in racy conditions, where different threads or Futures are trying to assign to the same OnceCell/Lock. The "loser" should be able to get the old and new value and determine if there is any difference or what that difference is.

static VALUE: OnceLock<Vec<u8>> = OnceLock::new();

if let Some(value) = VALUE.get() {
    println!("report value: {value:?}");
} else {
    match VALUE.try_insert(calculate_value()) {
        Ok(value) => println!("report value: {value:?}");
        // Some other thread has won the race to determine this value first.
        Err(old_value, new_value) => assert_eq!(old_value, new_value, "somehow calculated values differed"),
    }
}

Solution sketch

impl<T> OnceCell/Lock<T> {
    pub fn try_insert(&self, value: T) -> Result<&T, (&T, T)>;
}

Alternatives

This proposal offers an optimization, but it's perfectly possible to do this with the existing methods:

if let Some(value) = VALUE.get() {
    println!("report value: {value:?}");
} else {
    match VALUE.set(calculate_value()) {
        Ok(value) => println!("report value: {value:?}");
        // Some other thread has won the race to determine this value first.
        Err(new_value) => assert_eq!(value.get().unwrap(), new_value, "somehow calculated values differed"),
    }
}

try_insert() would offer an optimized path that doesn't require an additional unwrap().

This method is currently available in once_cell, which as far as I'm aware was the original inspiration for std::cell::OnceCell and std::sync::OnceLock.

Links and related work

BurntSushi commented 11 months ago

Seconded. (I will say though that I am somewhat skeptical of this routine and it isn't clear that it pulls its weight. On the path to stabilization, I'd be interested in both how much this optimization buys us and how often it would be used.)