standard lazy types by matklad · Pull Request #2788 · rust-lang/rfcs (original) (raw)

Expand Up

@@ -245,8 +245,7 @@ Non thread-safe flavor can be added to `core` as well.

The thread-safe variant is implemented similarly to `std::sync::Once`.

Crucially, it has support for blocking: if many threads call `get_or_init` concurrently, only one will be able to execute the closure, while all other threads will block.

For this reason, `std::sync::OnceCell` can not be provided in core.

Even the minimal `OnceCell::::set` API requires support for blocking, because one can't atomically set arbitrary `T`.

For this reason, most of `std::sync::OnceCell` API can not be provided in `core`.

# Drawbacks

[drawbacks]: #drawbacks

Expand Down Expand Up

@@ -335,6 +334,48 @@ That is, we can store some initial state in the cell and consume it during initi

In practice, such flexibility seems to be rarely required.

Even if we add a type, similar to `OnceFlipCell`, having a dedicated `OnceCell` (which *could* be implemented on top of `OnceFlipCell`) type simplifies a common use-case.

## Variations of `set`

The RFC proposes "obvious" signature for the `set` method:

```rust

fn set(&self, value: T) -> Result<(), T>;

```

Note, however, that `set` establishes an invariant that the cell is initialized, so a more precise signature would be

```rust

fn set(&self, value: T) -> (&T, Option);

```

To be able to return a reference, `set` might need to block a thread.

For example, if two threads call `set` concurrently, one of them needs to block while the other moves the value into the cell.

It is possible to provide a non-blocking alternative to `set`:

```rust

fn try_set(&self, value: T) -> Result<&T, (Option<&T>, T)>

```

That is, if value is set successfully, a reference is returned.

Otherwise, ther the cell is either fully initialized, and a reference is returned as well, or the cell is being initialized, and no valid reference exist yet.

## Support for `no_std`

The RFC proposes to add `cell::OnceCell` and `cell::Lazy` to `core`, while keeping `sync::OnceCell` and `sync::Lazy` `std`-only.

However, there's a subset of `sync::OnceCell` that can be provided in `core`:

```rust

impl OnceCell {

const fn new() -> OnceCell;

fn get(&self) -> Option<&T>;

fn try_set(&self, value: T) -> Result<&T, (Option<&T>, T)>

}

```

It is possible because, while `OnceCell` needs block for full API, its internal state can be implemented as a single `AtomicUsize`, so the `core` part does not need to know about blocking.

It is unclear if this API would be significantly useful.

In particular, the guarantees of non-blocking `set` are pretty weak, and are not enough to implement the `Lazy` wrapper.

## Poisoning

As a cell can be empty or fully initialized, the proposed API does not use poisoning.

Expand Down