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

standard lazy types #2788

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open

standard lazy types #2788

wants to merge 13 commits into from

Conversation

@matklad
Copy link
Member

@matklad matklad commented Oct 18, 2019

Add support for lazy initialized values to standard library, effectively superseding the popular lazy_static crate.

use std::sync::Lazy;

// `BACKTRACE` implements `Deref<Target = Option<String>>` 
// and is initialized on the first access
static BACKTRACE: Lazy<Option<String>> = Lazy::new(|| {
    std::env::var("RUST_BACKTRACE").ok()
});

Rendered

text/0000-standard-lazy-types.md Outdated Show resolved Hide resolved
text/0000-standard-lazy-types.md Outdated Show resolved Hide resolved
text/0000-standard-lazy-types.md Outdated Show resolved Hide resolved
text/0000-standard-lazy-types.md Outdated Show resolved Hide resolved
text/0000-standard-lazy-types.md Outdated Show resolved Hide resolved
text/0000-standard-lazy-types.md Show resolved Hide resolved
text/0000-standard-lazy-types.md Show resolved Hide resolved
Co-Authored-By: Mazdak Farrokhzad <twingoow@gmail.com>
@pitdicker
Copy link
Contributor

@pitdicker pitdicker commented Oct 18, 2019

Great RFC, I hope it makes it into the standard library!

One other aspect that the crate conquer_once tackles is a distinction between blocking and non-blocking methods. I believe this is one possible way to divide the api?

    /// Never blocks, returns `None` if uninitialized, or while another thread is initializing the `OnceCell` concurrently.
    pub fn get(&self) -> Option<&T>;

    /// Never blocks, returns `Err` if initialized, or while another thread is initializing the `OnceCell` concurrently.
    pub fn set(&self, value: T) -> Result<(), T>;

    /// Blocks if another thread is initializing the `OnceCell` concurrently.
    pub fn get_or_init<F>(&self, f: F) -> &T
    where
        F: FnOnce() -> T,
    ;

    /// Blocks if another thread is initializing the `OnceCell` concurrently.
    pub fn get_or_try_init<F, E>(&self, f: F) -> Result<&T, E>
	where
        F: FnOnce() -> Result<T, E>,
    ;
}

Alternatively, all four methods could be blocking, and a try_get and try_set may suffice for a non-blocking use case.

But don't let this comment derail the discussion about whether to include OnceCell in the standard library to much.

@SimonSapin
Copy link
Contributor

@SimonSapin SimonSapin commented Oct 18, 2019

fn pointers are not ZSTs, so we waste one pointer per static lazy value. Lazy locals will generally rely on type-inference and will use more specific closure type.

It looks like rust-lang/rust#63065 will allow using a zero-size closure type in a static:

static FOO: Lazy<Foo, impl FnOnce() -> Foo> = Lazy::new(|| foo());

But this will require spelling the item type twice. Maybe that repetition could be avoided… with a macro… that could be named lazy_static! :)

@RustyYato
Copy link

@RustyYato RustyYato commented Oct 18, 2019

@SimonSapin or we can add bounds to Lazy like so

struct Lazy<F: FnOnce<()>> { .. }

and use it like this,

static FOO: Lazy<impl FnOnce() -> Foo> = Lazy(foo);

@SimonSapin
Copy link
Contributor

@SimonSapin SimonSapin commented Oct 18, 2019

@KrishnaSannasi How does that help with the requirement that static items name their type without inference?

static FOO: &[_] = &[2, 4];

(Playground)

   Compiling playground v0.0.1 (/playground)
error[E0121]: the type placeholder `_` is not allowed within types on item signatures
 --> src/lib.rs:1:15
  |
1 | static FOO: &[_] = &[2, 4];
  |               ^ not allowed in type signatures

@RustyYato
Copy link

@RustyYato RustyYato commented Oct 19, 2019

@SimonSapin I'm not sure what you are asking, I didn't use _ anywhere in my example. It would be nice if we could have type inference in static/const that only depended on their declaration, but we don't have that (yet).

You proposed using impl Trait to get rid of the cost of fn() -> ..., and noted a paper cut where you would have to name a type multiple times, and I proposed a way to get rid of that paper cut by changing Lazy by removing the redundant type parameter.

@sfackler
Copy link
Member

@sfackler sfackler commented Oct 19, 2019

It doesn't really seem all that worth it to me to spend time worrying about how to inline a function that is called at most one time in the entire execution of a program.

text/0000-standard-lazy-types.md Outdated Show resolved Hide resolved
@pitdicker
Copy link
Contributor

@pitdicker pitdicker commented Oct 20, 2019

A small difference that may be worth noting: std::sync::Once will only run its closure once, while OnceCell::get_or{_try}_init will one run its closure once successfully.

@matklad
Copy link
Member Author

@matklad matklad commented Oct 21, 2019

@pitdicker pointed out that we actually can have some part of the sync API exposed via core. This unfortunately increases design/decision space a bit :) I've amended the rfc.

text/0000-standard-lazy-types.md Outdated Show resolved Hide resolved
@tarcieri
Copy link

@tarcieri tarcieri commented Oct 21, 2019

@jhpratt the author of this RFC @matklad is the author of once_cell. It also mentions:

The proposed API is directly copied from once_cell crate.

Altogether, this RFC proposes to add four types:

* `std::cell::OnceCell`, `std::cell::Lazy`
* `std::sync::OnceCell`, `std::sync::Lazy`

Choose a reason for hiding this comment

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

I see that we have sync::Once and cell::Cell, but this may be confusing. Just a thought.

Choose a reason for hiding this comment

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

This is kinda like the combination of both, so the naming may be good (modulo Send/Sync concerns). But it would be better if the !Sync version had a different name from the Sync version.

Copy link
Contributor

@clarfonthey clarfonthey Oct 22, 2019

Choose a reason for hiding this comment

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

Perhaps acknowledging OnceCell as a WORM (write once, read many) might make WormCell be a better name? Assuming that the implementation isn't too sluggish.

Copy link
Contributor

@clarfonthey clarfonthey Oct 22, 2019

Choose a reason for hiding this comment

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

I also would really like to see there be a different name for these, rather than relying on the fact that they lie in two separate modules. Maybe just prefixing the sync implementations with Sync is enough, i.e. SyncLazy and Lazy.

Copy link
Member Author

@matklad matklad Oct 23, 2019

Choose a reason for hiding this comment

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

I would be against WORM terminology, as it seems rather obscure: I don't think I see it often enough to not consult wikipedia. A more appropriate term would probably be SingleAssignmentCell, but that's a mouthful.

Ultimately, I doubt that there's a perfect solution for cell/sync naming, so the libs team would have to just decide on some particular naming scheme. I am personally ok with Sync prefix. The disadvantage of SyncX scheme is that it makes more common case longer to spell, and is a tautology (sync::SyncOnceCell).

I personally like the proposed cell::OnceCell / sync::OnceCell variant the most:

  • if you want extra clarity, you can qualify the type with cell:: or sync:: on the call site, instead of importing it
  • due to how awesome Rust is, it's impossible to use cell::OnceCell instead of sync::OnceCell, and the mistake in other direction is not really a mistake: just a very slight perf loss
  • in practice, I think it would be rare for a module to use cell and sync versions simultaneously.

Copy link

@nixpulvis nixpulvis Oct 23, 2019

Choose a reason for hiding this comment

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

I think I like the way it's organized in the once_cell crate with explicit sync::* and unsync::* modules.

The part that feels the most strange to me about this currently proposal is that a OnceCell lives in both cell (makes sense, since it's a type of cell), and sync (makes sense, given that Once is also defined here). Something's conflated here, though this could well be outside the scope of this RFC. Either way, this is the first "cell" added to the sync module.

For another point of reference, we currently have Rc defined in std::rc and a similar Arc defined in std::sync. I might argue this would make more sense as std::rc::{Rc, sync::Arc}, which would solve my issue with OnceCell as well, though I don't expect this to be possible with backwards compatibility requirements.

Copy link
Member Author

@matklad matklad Oct 23, 2019

Choose a reason for hiding this comment

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

Yeah, another possibility is to introduce something like lazy and lazy::sync modules. As it stands, std::sync today feels a bit like, well, kitchen sync, it has a bit of everything.

I can imagine an alternative world, where std::sync is only for synchronization primitives, atomic and mpsc are top-level modules and Arc lives in rc. In this world, having an std::lazy module would definitely be better.

With current stdlib structure, std::sync::OnceCell blends better with the background.

Copy link
Contributor

@SimonSapin SimonSapin Oct 25, 2019

Choose a reason for hiding this comment

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

Also, everything in std::cell is !Sync so it sort of maps to crates.io’s once_cell::unsync.

Copy link
Member

@sfackler sfackler Oct 25, 2019

Choose a reason for hiding this comment

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

The libs team has discussed moving stuff out of std::sync into more appropriate modules (e.g. std::mutex::Mutex). I think ideally we wouldn't throw more stuff in there.

@wolfiestyle
Copy link

@wolfiestyle wolfiestyle commented Oct 26, 2019

I was expecting this to implement haskell-style lazy values. Can I use this outside global scope? Maybe use another more descriptive name? like LazyStatic

Under release/acquire, the assert never fires.
Under release/consume, it might fire.

Release/consume can potentially be implemented more efficiently on weak memory model architectures.
However, the situation with `consume` ordering is cloudy right now:

* [nobody knows what it actually means](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0371r0.html),
* [but people rely on it in practice for performance](https://docs.rs/crossbeam-utils/0.7.0/crossbeam_utils/atomic/trait.AtomicConsume.html#tymethod.load_consume).

We can do one of the following:

1. Specify and implement `acquire` ordering,
2. Specify `consume` but implement `acquire` (or hack `consume` in an implementation-defined manner) with the hope to make implementation more efficient later.
3. Specify and implement `acquire`, but provide additional API which can take `Ordering` as an argument.
Copy link
Contributor

@workingjubilee workingjubilee Sep 2, 2020

Choose a reason for hiding this comment

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

The exact definition of "depend on" is a bit vague, but it works as you would expect in practice since a lot of software, especially the Linux kernel, rely on this behavior.

  • crossbeam_utils::AtomicConsume::load_consume

It is widely accepted that the current definition of memory_order_consume in the standard is not useful. All current compilers essentially map it to memory_order_acquire.
...
Currently there is no way to express this code in a way that is both performant and standard-conforming.
...
It is likely that any such revision will not be fully backwards-compatible with the old one, but will better support common use cases.

  • the deprecation proposal for memory_order_consume

The links are helpful, but examining them raises more confusion. And some architectures Rust supports default to acquire-based semantics, so someone compiling on them would not expect the bugs that arise in another architecture because they always acquire. Others default to consume-like semantics, and run slower if forced to acquire. However, because of the former, it seems like embedding an implicit consume contract is an invitation for Heisenbugs because a user cannot actually know and try to test for if the access is consume-ordered or not, no?

Copy link
Member Author

@matklad matklad Sep 2, 2020

Choose a reason for hiding this comment

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

However, because of the former, it seems like embedding an implicit consume contract is an invitation for Heisenbugs because a user cannot actually know and try to test for if the access is consume-ordered or not, no?

This is true more or less about all atomics in Rust: most people test only on x86 (I certainly do), and it implements a relatively strong memory model.

Copy link
Contributor

@workingjubilee workingjubilee Sep 4, 2020

Choose a reason for hiding this comment

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

That's true, but a lot of the atomic APIs accept atomic::Ordering, and Rust doesn't expose a variant of atomic::Ordering::Consume, so it seems premature to specify it as a default.

And if someone writes code that works on one architecture but is different on another, well, arguably that's the breaks. It's rust-lang not miracle-lang. But specifying Consume, implementing Acquire, and then on one architecture relaxing to Consume when atomic::Ordering::Consume becomes available seems like deliberately breaking the API for one architecture in a way that may not be easily understandable, and testing is hard even if you have such a machine. So I suppose I will narrow it down further: specifying Consume as a default could be OK, but should that method remain unstable until an Ordering::Consume is also stabilized? I am not as sure about 2b, since it seems like a very different case in actuality.

I'm in favor of standard lazy types, it just seemed like something to think about.

Copy link
Contributor

@workingjubilee workingjubilee Jun 5, 2021

Choose a reason for hiding this comment

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

On the upside, since I made this comment, a bunch of aarch64 targets arrived in tier 1 and tier 2 with host tools, and at least one is even a popular developer platform, so debugging this admittedly seems more plausible.

dani-garcia added a commit to dani-garcia/madness that referenced this issue Oct 19, 2020
Migrate lazy_static to once_cell, which doesn't require macros and is closer to what Rust might add to the standard library rust-lang/rfcs#2788
Move rand dependency to dev-dependencies
mergify bot pushed a commit to gnosis/dex-services that referenced this issue Dec 9, 2020
Bumps [async-std](https://github.com/async-rs/async-std) from 1.7.0 to 1.8.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/async-rs/async-std/releases">async-std's releases</a>.</em></p>
<blockquote>
<h2>v1.8.0</h2>
<p>This patch introduces <code>async_std::channel</code>, a new submodule for our async channels implementation. <code>channels</code> have been one of async-std's most requested features, and have existed as &quot;unstable&quot; for the past year. We've been cautious about stabilizing channels, and this caution turned out to be warranted: we realized our channels could hang indefinitely under certain circumstances, and people ended up expressing a need for unbounded channels.</p>
<p>So today we're introducing the new <code>async_std::channel</code> submodule which exports the <code>async-channel</code> crate, and we're marking the older unstable <code>async_std::sync::channel</code> API as &quot;deprecated&quot;. This release includes both APIs, but we intend to stabilize <code>async_std::channel</code> and remove the older API in January. This should give dependent projects a month to upgrade, though we can extend that if it proves to be too short.</p>
<p>The rationale for adding a new top-level <code>channel</code> submodule, rather than extending <code>sync</code> is that the <code>std::sync</code> and <code>async_std::sync</code> submodule are a bit of a mess, and the libs team <a href="%5Bhttps://github-redirect.dependabot.com/rust-lang/rfcs/pull/2788#discussion_r339092478%5D(https://github-redirect.dependabot.com/rust-lang/rfcs/pull/2788#discussion_r339092478)">has been talking about splitting <code>std::sync</code> up</a> into separate modules. The stdlib has to guarantee it'll forever be backwards compatible, but <code>async-std</code> does not (we fully expect a 2.0 once we have async closures &amp; traits). So we're experimenting with this change before <code>std</code> does, with the expectation that this change can serve as a data point when the libs team decides how to proceed in std.</p>
<h3>Added</h3>
<ul>
<li><code>async_std::channel</code> as &quot;unstable&quot; <a href="https://github-redirect.dependabot.com/async-rs/async-std/issues/915">#915</a></li>
<li><code>async_std::process</code> as &quot;unstable&quot; <a href="https://github-redirect.dependabot.com/async-rs/async-std/issues/916">#916</a></li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Fixed mentions of the <code>tokio03</code> flags in the docs <a href="https://github-redirect.dependabot.com/async-rs/async-std/issues/909">#909</a></li>
<li>Fixed a double drop issue in <code>StreamExt::cycle</code> <a href="https://github-redirect.dependabot.com/async-rs/async-std/issues/903">#903</a></li>
</ul>
<h3>Internal</h3>
<ul>
<li>updated <code>pin-project</code> to <code>v0.2.0</code></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/async-rs/async-std/blob/master/CHANGELOG.md">async-std's changelog</a>.</em></p>
<blockquote>
<h1>[1.8.0] - 2020-12-04</h1>
<p>This patch introduces <code>async_std::channel</code>, a new submodule for our async channels implementation. <code>channels</code> have been one of async-std's most requested features, and have existed as &quot;unstable&quot; for the past year. We've been cautious about stabilizing channels, and this caution turned out to be warranted: we realized our channels could hang indefinitely under certain circumstances, and people ended up expressing a need for unbounded channels.</p>
<p>So today we're introducing the new <code>async_std::channel</code> submodule which exports the <code>async-channel</code> crate, and we're marking the older unstable <code>async_std::sync::channel</code> API as &quot;deprecated&quot;. This release includes both APIs, but we intend to stabilize <code>async_std::channel</code> and remove the older API in January. This should give dependent projects a month to upgrade, though we can extend that if it proves to be too short.</p>
<p>The rationale for adding a new top-level <code>channel</code> submodule, rather than extending <code>sync</code> is that the <code>std::sync</code> and <code>async_std::sync</code> submodule are a bit of a mess, and the libs team <a href="%5Bhttps://github-redirect.dependabot.com/rust-lang/rfcs/pull/2788#discussion_r339092478%5D(https://github-redirect.dependabot.com/rust-lang/rfcs/pull/2788#discussion_r339092478)">has been talking about splitting <code>std::sync</code> up</a> into separate modules. The stdlib has to guarantee it'll forever be backwards compatible, but <code>async-std</code> does not (we fully expect a 2.0 once we have async closures &amp; traits). So we're experimenting with this change before <code>std</code> does, with the expectation that this change can serve as a data point when the libs team decides how to proceed in std.</p>
<h3>Added</h3>
<ul>
<li><code>async_std::channel</code> as &quot;unstable&quot; <a href="https://github-redirect.dependabot.com/async-rs/async-std/issues/915">#915</a></li>
<li><code>async_std::process</code> as &quot;unstable&quot; <a href="https://github-redirect.dependabot.com/async-rs/async-std/issues/916">#916</a></li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Fixed mentions of the <code>tokio03</code> flags in the docs <a href="https://github-redirect.dependabot.com/async-rs/async-std/issues/909">#909</a></li>
<li>Fixed a double drop issue in <code>StreamExt::cycle</code> <a href="https://github-redirect.dependabot.com/async-rs/async-std/issues/903">#903</a></li>
</ul>
<h3>Internal</h3>
<ul>
<li>updated <code>pin-project</code> to <code>v0.2.0</code></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/async-rs/async-std/commit/fde2f58610ad8ddc1d7729d1cf6ca5e870f98ac3"><code>fde2f58</code></a> Merge pull request <a href="https://github-redirect.dependabot.com/async-rs/async-std/issues/917">#917</a> from async-rs/v1.8.0</li>
<li><a href="https://github.com/async-rs/async-std/commit/c738d73bd7a6ac465580135d59556528f01747d7"><code>c738d73</code></a> v1.8.0</li>
<li><a href="https://github.com/async-rs/async-std/commit/2757969f5d03a2b92b6a405074fbc820b4041009"><code>2757969</code></a> Merge pull request <a href="https://github-redirect.dependabot.com/async-rs/async-std/issues/918">#918</a> from async-rs/restore-prior-process-exports</li>
<li><a href="https://github.com/async-rs/async-std/commit/34e9ff3cd2aab8971a5e0338b2afbcb1725f8b7d"><code>34e9ff3</code></a> Restore sync process exports</li>
<li><a href="https://github.com/async-rs/async-std/commit/9cd0578826372e453497ab9b56edf618c794cae3"><code>9cd0578</code></a> Merge pull request <a href="https://github-redirect.dependabot.com/async-rs/async-std/issues/916">#916</a> from async-rs/async-process</li>
<li><a href="https://github.com/async-rs/async-std/commit/f8f1eacc9aa8b32a0987b1dd436fd6974d57dc4b"><code>f8f1eac</code></a> Attempt 2 at fixing docs on windows</li>
<li><a href="https://github.com/async-rs/async-std/commit/92f5038ed658ac75e7788cadacccaac7dda4430b"><code>92f5038</code></a> attempt to fix docs builds</li>
<li><a href="https://github.com/async-rs/async-std/commit/415d0d1e517b5862f7fd7a78513adad40a111571"><code>415d0d1</code></a> Merge pull request <a href="https://github-redirect.dependabot.com/async-rs/async-std/issues/732">#732</a> from hhggit/timeout_repeat</li>
<li><a href="https://github.com/async-rs/async-std/commit/6ae69c94d2f855f8ba1533f77d05cf46adbb3a10"><code>6ae69c9</code></a> Merge pull request <a href="https://github-redirect.dependabot.com/async-rs/async-std/issues/915">#915</a> from async-rs/feat-new-channels</li>
<li><a href="https://github.com/async-rs/async-std/commit/151025fa38c98eb519053f7d151054e8f7b14f91"><code>151025f</code></a> fixup</li>
<li>Additional commits viewable in <a href="https://github.com/async-rs/async-std/compare/v1.7.0...v1.8.0">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://api.dependabot.com/badges/compatibility_score?dependency-name=async-std&package-manager=cargo&previous-version=1.7.0&new-version=1.8.0)](https://dependabot.com/compatibility-score/?dependency-name=async-std&package-manager=cargo&previous-version=1.7.0&new-version=1.8.0)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
- `@dependabot use these labels` will set the current labels as the default for future PRs for this repo and language
- `@dependabot use these reviewers` will set the current reviewers as the default for future PRs for this repo and language
- `@dependabot use these assignees` will set the current assignees as the default for future PRs for this repo and language
- `@dependabot use this milestone` will set the current milestone as the default for future PRs for this repo and language
- `@dependabot badge me` will comment on this PR with code to add a "Dependabot enabled" badge to your readme

Additionally, you can set the following in your Dependabot [dashboard](https://app.dependabot.com):
- Update frequency (including time of day and day of week)
- Pull request limits (per update run and/or open at any time)
- Out-of-range updates (receive only lockfile updates, if desired)
- Security updates (receive only security updates, if desired)



</details>
@rodrimati1992
Copy link

@rodrimati1992 rodrimati1992 commented Dec 28, 2020

I'd expect the Sync version of Lazy to be used more than the !Sync version, so renaming them to Lazy and UnsyncLazy would be good.

@clarfonthey
Copy link
Contributor

@clarfonthey clarfonthey commented Dec 28, 2020

I'm not a fan of Unsync naming unless the rest of libstd follows suit. We already separate it out that way, e.g. Arc and Rc are used instead of Rc and Narc (non-atomic reference counting).

@mettke
Copy link

@mettke mettke commented Jan 10, 2021

We also may want to consider adding an Entry API similar to the one of HashMap. That way its easier to deal with async operations:

let data: OnceCell<Data> = OnceCell::new();
let inner = match data.entry() {
   Entry::Occupied(entry) => entry.get(),
   Entry::Vacant(entry) => {
      let value = operation().await?;
      entry.insert(value)
   }
}

@nwn
Copy link

@nwn nwn commented Jan 10, 2021

@mettke The VacantEntry::insert method would need to be try_insert since the cell could be initialized during the async operation.

But your use case might be better served by the Lazy type.

@rkjnsn
Copy link
Contributor

@rkjnsn rkjnsn commented Feb 28, 2021

For the Sync variant of OnceCell, I think either set should block, or the documentation needs to make it extremely clear that set returning does not guarantee that a subsequent call to get will return a value.

Given the documentation as currently written, I could very easily see myself assuming that the following would work:

// Don't need my_value back if some_once_cell is already set.
let _ = some_once_cell.set(my_value);
// Now I [erroneously think I] know some_once_cell is set.
let value = some_once_cell.get().unwrap();

My preference would be for set to block to avoid a footgun, and provide a separate, non-blocking method if needed. In this case, it probably makes sense to use the fn set(&self, value: T) -> (&T, Option<T>) variant of set, in which case my example could be simplified to the following:

// Don't need my_value back if some_once_cell is already set.
let (value, _) = some_once_cell.set(my_value);

@matklad
Copy link
Member Author

@matklad matklad commented Feb 28, 2021

@rkjnsn set does block, but calling that in the docs explicitly would help. Do you want to set a PR? ^^

@rkjnsn
Copy link
Contributor

@rkjnsn rkjnsn commented Feb 28, 2021

Oh, neat. Sent rust-lang/rust#82645

I do think I would still lean toward fn set(&self, value: T) -> (&T, Option<T>) for the signature, but blocking at least makes it less footgun-y than what I gathered from reading the discussion.

@DesmondWillowbrook
Copy link

@DesmondWillowbrook DesmondWillowbrook commented Jun 4, 2021

What is the progress on this PR?

@burdges
Copy link

@burdges burdges commented Jun 5, 2021

As lazy static incurs some runtime cost, it's only suitable for improving code structure. You need static_init when you need performance, but static_init provides a lower level interface than most folks like. It lacks priorities on Mac OS but not sure if this ever really matters.

nars1 pushed a commit to YottaDB/YDBRust that referenced this issue Jun 9, 2021
- Switch from `#[serial]` to RwLock

  This orders the tests which must be run in serial with respect to
  *all* other tests, not just tests marked with `#[serial]`. In
  particular, when a test has a write lock out, no other test will be
  running concurrently. This is different from a mutex, where either a
  read or write lock will prevent other concurrent tests, because there
  can be multiple tests with a read lock running concurrently. Previously,
  `serial_test` would only prevent tests marked with `#[serial]` from
  running concurrently with each other; it was possible for another test
  to run in parallel with a serial test.

- Add a global `TEST_LOCK` using `once_cell`

  `once_cell` is a dependency from crates.io. `"1"` is the version
  number, and means "any version of `once_cell` with a major version of 1".

  There is more documentation about once_cell itself at
  https://docs.rs/once_cell/1/once_cell/. It is a very commonly used
  crate, and there are proposals to make it part of the standard library:
  rust-lang/rfcs#2788.

- Add `Context::write_lock` wrapper

  `write_lock` is basically the same as `LockGuard::write`, but is
  stored in the `Context` and dropped automatically. This is not
  particularly more convenient; the reason I added it is so I could put a
  read lock in Context by default, to avoid accidentally missing a lock
  (like I did for the `simple_api` originally). Note that write_lock takes
  care of dropping the read lock so the tests won't deadlock; I originally
  had issues with deadlocks before adding the wrapper function.

- Don't use conflicting variable names for tests
- Fix races in `simple_api::call_in` tests with write guards

  Previously, the following error would occur about once every 5 runs:

  ```
  ---- simple_api::call_in::test::string_args stdout ----
  thread 'simple_api::call_in::test::string_args' panicked at 'called `Result::unwrap()` on an `Err` value: YDB Error (-150379666): 150379666,(SimpleThreadAPI),%YDB-E-CINOENTRY, No ', src/simple_api/call_in.rs:315:18
  note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
  ```

  The issue was that multiple tests were modifying the active call-in
  table. Now that the tests take out a write lock, the lock ensures that
  no two tests will be running at the same time when the call-in table is
  modified. This is slightly heavier of a hammer than necessary, because
  only tests making call-ins could possibly interfere, but the test suite
  runs fast enough I don't think it's worth improving.

- Add read guards to all other tests that have asserts

  If a test does not take out either a read or write lock, it could run
  concurrently with tests with a write lock. There are a very few tests
  that can do this without failing. For example, `can_make_key` does not
  ever make a call to the YDB engine, so it can't view any process-wide
  state, making it safe to run unsynchronized. Any test doing `get`s or
  `set`s could fail though, because the `ydb_delete_excl_st` test deletes
  *all* process-local variables. In other words, adding an `assert!` and
  read lock is the rule; not needing a read lock is the exception.
```
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.
Copy link

@bl-ue bl-ue Jun 17, 2021

Choose a reason for hiding this comment

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

Suggested change
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.
Otherwise, 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.

@bstrie
Copy link
Contributor

@bstrie bstrie commented Jul 21, 2021

So I've been a bit confused about the state of this RFC, given that an implementation exists over at rust-lang/rust#74465 (comment) and it seems to be approaching the point where it could be considered for a stabilization FCP. From what I can tell it seems accurate to say that the libs team expressed such a strong desire to see this pursued that they invited an implementation into the Rust repo itself to hash out the final details of the API, and then after enough time the RFC itself fell through the cracks and iteration and discussion happened solely on the tracking issue in the Rust repo without anyone remembering that the RFC was still open. So I think the best way to think about this RFC is that it was "implicitly accepted", and has long since progressed towards implementation and stabilization.

It's a bit of an accidental breach of protocol, since the closest precedent that we have is the eRFC process, which allows implementations in the Rust repo but which still require an accepted RFC before stabilization. Of course the API here isn't really large enough to warrant an eRFC, and it's still up to the libs team to draw the line as to what changes actually need RFCs, so it's not out of the question for them to simply say "this change didn't require an RFC" and call it a day. Still, should this RFC be merged on principle (as outdated as it is), or should it simply be closed?

In any case, anyone interested in discussing this API further should congregate in the tracking issue at rust-lang/rust#74465 , in order to centralize discussion. I intend to write a comment soon proposing a final API here with a follow-up PR implementing it. If that is merged it may soon come up for FCP and hopefully stabilization, per the usual process.

@konsumlamm
Copy link

@konsumlamm konsumlamm commented Jan 4, 2022

Is there any particular reason why there is no DerefMut impl for Lazy (once_cell has one, see matklad/once_cell#86) and no way to extract the inner value? Or are these planned to be added later, when this minimal version is stable?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment