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
Tracking Issue for once_cell
#74465
Comments
|
Let's cross-out the "should get be blocking?" concern. I decided against this for once_cell, for the following reasons:
|
|
Added two more open questions from the RFC. |
This reverts commit fe63905.
|
I've added a summary of proposed API to the issue description. I wonder if makes sense for @rust-lang/libs to do a sort of "API review" here: this is a pretty big chunk of API, and we tried to avoid bike shedding on the RFC. |
|
Here's an interesting use-case for non-blocking subset of OnceCell -- building cyclic data structures: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=4eceeefc224cdcc719962a9a0e1f72fc |
|
I strongly expect a method called |
|
Yeah, to be clear, there's a consensus that |
I don't think I'd expect something that's named AtomicLazy/AtomicOnceCell to do the same. And that's something that already exists as another valid strategy for certain Lazy/OnceCell-like types: Instead of blocking all but one thread when multiple threads encounter an 'empty' cell, it wouldn't block but run the initialization function on each of these threads. The first thread to finish atomically stores its initialized value in the cell, and the others simply The rust/library/std/src/sys/windows/compat.rs Lines 65 to 67 in a835b48
And here: rust/library/std/src/sys/windows/mutex.rs Lines 119 to 133 in a835b48
And another example in So, since this Lazy/OnceCell implementation does block (such that the initialization function can be |
|
I've added non-blocking flavors of the primitives to the once_cell crate: https://docs.rs/once_cell/1.5.1/once_cell/race/index.html. They are restricted (can be provided only for atomic types), but are compatible with no_std. It seems to me that "first one wins" is a better semantics if you can't block, so I am going to resolve
I've ticked this question's box. |
|
It's a bit of a shame that |
This seems like a very major restriction, which rules out most use cases of
I agree that spinlocks have their problems, but they're still better than using This could be implemented using a second generic argument on the |
|
Some thoughts about These types have both Maybe if the |
|
Unresolved question: method naming Currently, we have
1. Pro: Status Quo, name specific to I've though more about this, and I think I actually like 3 most. It's Con seems like a Pro to me. In the typical use-case, you only use impl Spam {
fn get_eggs(&self) -> &Eggs {
self.eggs.get_with(|| Eggs::cook())
}
}So, the closure is sort-of always called, it's just cached. Not sure if I my explanation makes sense, but I do feel that this is different from, eg, |
|
@phil-opp: I think it is rather certain that, even if std provides a subset of OnceCell for no_std, it will be non-blocking subset ( It certainly is possible to use spinlocks, or make sync::OnceCell parametric (compile-time or run-time) over blocking primitives. I am pretty sure that should be left for crates.io crate though. I feel one important criterion for inclusion in std is "design space has a solution with a single canonical API". OnceCell API seem canonical. If we add paramters, the design space inflates. Even if some solution would be better, it won't be obviously canonical, and would be better left to crates.io. |
@m-ou-se yeah, totally agree that this is a hack and feels like a hack. It works well enough in practice, but there's one gotcha: specifying type for a local lazy does not work: let x = 92;
let works1: = Lazy::new(|| x.to_string());
let broken: Lazy<String> = Lazy::new(|| x.to_string());
let works2: Lazy<String, _> = Lazy::new(|| x.to_string());The One easy way out here is to stabilize only fn global_state() -> &'static GlobalState {
static INSTANCE: SyncOnceCell<GlobalState> = SyncOnceCell::new();
INSTANCE.get_or_init(GlobalState::default)
}doesn't feel like a deal breaker.I'd prefer that to pulling a 3rd party dep (lazy_staic or once_cell). That said, I think static GLOBAL_STATE: Lazy<GlobalState, _> = Lazy::new(GlobalState::default);I don't see a lot of practical problems with static GLOBAL_STATE: Lazy<GlobalState> = Lazy::new(GlobalState::default);working as well. |
I think 1 is the most appropriate. The
This doesn't seem very intuitive to me and isn't always true when there are multiple points of initialization. For example, consider: impl Spam {
fn get_eggs(&self, cooked: bool) -> &Eggs {
if cooked {
self.eggs.set(Eggs::cook());
}
self.eggs.get_with(|| Eggs::raw())
}
}In this case, the closure may not run and in fact a different value has been cached. I think |
|
Something I've recently got bitten by is the need to manage which memory allocator a memory uses. I've been workign wit ha design that has a different global memory allocator when running threads or coroutines (so restricting a coroutine to a maximum amount of memory). This could be thought of as a bit of a hack; one of the long-term design decisions of early Rust that still bites is not making the memory allocator type explicit in the standard collections. With a lazy, the challenge becomes ensuring that they're all allocated using the same memory allocator. |
The C standards require the address of the first field to be the address of the structure, which is why I suggested Anyway, I don't have a strong opinion about whether to do extra work to support the ability of non-Rust code to be able to access the value. |
|
I don't think there's much benefit to providing any sort of guarantee on internal layout - any alternative to For any Rust + C project that already has a good reason to use a Rust |
Piggybacking on the [motivation in winit]: `lazy_static!` is a macro whereas `once_cell` achieves the same using generics. Its implementation has also been [proposed for inclusion in `std`], making it easier for us to switch to a standardized version if/when that happens. The author of that winit PR is making this change to many more crates, slowly turning the scales in favour of `once_cell` in our dependency tree too. Furthermore `lazy_static` hasn't published any updates for 3 years, and the new syntax is closer for dropping this wrapping completely when the necessary constructors become `const` (i.e. switching to `parking_lot` will give us a [`const fn new()` on `RwLock`]) or this feature lands in stable `std`. [motivation in winit]: rust-windowing/winit#2313 [proposed for inclusion in `std`]: rust-lang/rust#74465 [`const fn new()` on `RwLock`]: https://docs.rs/lock_api/latest/lock_api/struct.RwLock.html#method.new
|
I've opened partial stabilization PR #105587 for |
|
Can we add |
Wouldn't |
|
Maybe for others, but for my use case I specifically need to take ownership. |
|
Went ahead and opened a PR: #106152 |
|
If you have ownership is it useful to have a |
|
Well yeah but then I have to manage lazy initialization myself. |
|
TIL! That's ever so slightly more annoying b/c you have to carry around the option and closure separately (or wrap them in your own type), but I'd be ok with having my PR closed if we think you should use this instead. |
Add #[inline] markers to once_cell methods Added inline markers to all simple methods under the `once_cell` feature. Relates to rust-lang#74465 and rust-lang#105587 This should not block rust-lang#105587
Add #[inline] markers to once_cell methods Added inline markers to all simple methods under the `once_cell` feature. Relates to rust-lang#74465 and rust-lang#105587 This should not block rust-lang#105587
FWIW, I use This kind of use case might disappear once |
|
I have a question about using get_or_init in the OnceLock struct. My use case involves two Tasks producing values (lhs and rhs), and I need to reduce the redex once both values are available. The order of these values becoming available is unknown. Could OnceLock be used here? I was thinking of using one OnceLock shared between two Tasks, and once a Task produces its value, it calls get_or_init. If the OnceLock is empty, it will be set, otherwise, I would get the existing value. However, I am not sure how to determine which value was returned in order to process the redex. As I understand it, after the get_or_init call, the (Boxed) value will be moved, and I can't compare pointers. My question is: could get_or_init take an Option with the current value as a parameter, or is there a way to map over OnceLock to either use or set its value? |
|
It just realized that in my specific case, one of the values has a positive polarity and the other one negative. So I can use OnceLock as is by checking the polarity of the cell in the OnceLock. In any case, I think the question above still holds. |
lazy_static has better ergonomics at the call/access sites (it returns a reference to the type directly, whereas with once_cell we get a static Lazy<T> that we must dereference instead) but the once_cell api is slated for integration into the standard library [0] and has been the "preferred" way to declare static global variables w/ deferred initialization. It's also less opaque and easier to comprehend how it works, I guess? (Both `once_cell` and `lazy_static` are already in our dependency tree, so this should have no detrimental effect on build times. It actually negligibly *improves* build times by not using macros, reducing the amount of expansion the compiler has to do by a miniscule amount.) [0]: rust-lang/rust#74465
More similar to the upcoming std implementation: rust-lang/rust#74465
More similar to the upcoming std implementation: rust-lang/rust#74465
This is a tracking issue for the RFC "standard lazy types" (rust-lang/rfcs#2788).
The feature gate for the issue is
#![feature(once_cell)].Unstable API
Steps
Unresolved Questions
Inlined from #72414:
Syncprefix likeSyncLazyfor now, but have a personal preference forAtomiclikeAtomicLazy.std::synctypes that we might want to just avoid upfront forstd::lazy, especially if that would align with a futurestd::mutexthat doesn't poison. Personally, if we're adding these types tostd::lazyinstead ofstd::sync, I'd be on-board with not worrying about poisoning instd::lazy, and potentially deprecatingstd::sync::Onceandlazy_staticin favour ofstd::lazydown the track if it's possible, rather than attempting to replicate their behavior. cc @Amanieu @sfackler.SyncOnceCell::getblocking. There doesn't seem to be consensus in the linked PR on whether or not that's strictly better than the non-blocking variant. (resolved in Tracking Issue foronce_cell#74465 (comment)).Release/Acquire, but it could also use the elusive Consume ordering. Should we spec that we guaranteeRelease/Acquire?SyncOnceCellin no_std. I think there's consensus that we don't want to include "blocking" parts of API, but it's unclear if non-blocking subset (get+set) would be useful. (resolved in Tracking Issue foronce_cell#74465 (comment)).get_or[_try]_initthe best name?Implementation history
UnwindSafeboundsThe text was updated successfully, but these errors were encountered: