Closed
Description
Note: lazy_cell_consume
is now tracked at #125623
This supercedes #74465 after a portion of once_cell
was stabilized with #105587
Feature gate: #![feature(lazy_cell)]
This is a tracking issue for the LazyCell
and LazyLock
types, which are designed to support convenient one-time initialization. One of the main goals is to be able to replace the lazy_static
crate, as well as once_cell::{sync, unsync}::Lazy
.
Public API
// core::cell (in core/src/cell/lazy.rs)
pub struct LazyCell<T, F = fn() -> T> { /* ... */ }
impl<T, F: FnOnce() -> T> LazyCell<T, F> {
pub const fn new(init: F) -> LazyCell<T, F>;
pub fn force(this: &LazyCell<T, F>) -> &T;
}
impl<T, F: FnOnce() -> T> Deref for LazyCell<T, F> {
type Target = T;
}
impl<T: Default> Default for LazyCell<T>;
impl<T: fmt::Debug, F> fmt::Debug for LazyCell<T, F>;
// std::sync (in std/sync/lazy_lock.rs)
pub struct LazyLock<T, F = fn() -> T> { /* ... */ }
impl<T, F: FnOnce() -> T> LazyLock<T, F> {
pub const fn new(f: F) -> LazyLock<T, F>;
pub fn force(this: &LazyLock<T, F>) -> &T;
}
impl<T, F> Drop for LazyLock<T, F>;
impl<T, F: FnOnce() -> T> Deref for LazyLock<T, F> {
type Target = T;
}
impl<T: Default> Default for LazyLock<T>;
impl<T: fmt::Debug, F> fmt::Debug for LazyLock<T, F>;
// We never create a `&F` from a `&LazyLock<T, F>` so it is fine
// to not impl `Sync` for `F`
unsafe impl<T: Sync + Send, F: Send> Sync for LazyLock<T, F>;
// auto-derived `Send` impl is OK.
impl<T: RefUnwindSafe + UnwindSafe, F: UnwindSafe> RefUnwindSafe for LazyLock<T, F>;
impl<T: UnwindSafe, F: UnwindSafe> UnwindSafe for LazyLock<T, F>;
Steps / History
- Implementation: Add lazy initialization primitives to std #72414Final comment period (FCP)1Stabilization PR - Stabilize
LazyCell
andLazyLock
#121377
Unresolved Questions
- Is variance of Lazy correct? (See Feature request: Make
Lazy<T, F>
covariant inF
matklad/once_cell#167)DefaultF = fn() -> T
in type signature (See Tracking Issue forlazy_cell
#109736 (comment))
Activity
once_cell
#105587tgross35 commentedon Mar 29, 2023
cc original authors @matklad and @KodrAus, just for reference
matklad commentedon Mar 29, 2023
The biggest design question here is the default parameter:
F = fn() -> T
static MY_DATA: Lazy<MyType> = ...
syntax work.static MY_DATA: Lazy<MyType, _>
working one day, but at this point it seems more likely than not that we won't ever implement this kind of inference, and, even if we end up implementing something like that, it would be years in the future.tgross35 commentedon Mar 29, 2023
It is kind of weird to have the
F
be a part of the type definition at all. It makes sense of course as far as needing to store the function type in the struct, but it's a bit clunky for type signatures.Is there a workaround using with raw function pointers maybe? Since the signature is always known, but I'm not sure how things like closure coercions & lifetimes would work here.
Or maybe dynamics could work. I haven't thought it totally through (and there might be lifetime trickiness) but at least the std
LazyLock
could store aBox<dyn FnOnce() -> T>
Sp00ph commentedon Mar 30, 2023
Boxing the contents would make it unusable for static values which would render it useless for most use cases.
tgross35 commentedon Mar 30, 2023
Yeah, that is a good point. And
&dyn
isn't very elegant.Just spitballing here... taking a
fn() -> T
would eliminate the second parameter. This would unfortunately mean that it can't take environment-capturing closures, blocking the examples matklad pointed out. At least forLazyLock
though, I can't really envision any use cases where this would be desired anyway. And it would allow for a nonbreaking upgrade toFnOnce
in the future, if there is ever a better way.playground
Sp00ph commentedon Mar 30, 2023
I don't see how that would be preferable over just having the second type parameter with the
fn() -> T
default. Granted, the case mentioned by @matklad currently doesn't provide a very good diagnostic, but if the compiler just suggested to add the_
as the second type parameter then that point of confusion would probably also largely disappear.SUPERCILEX commentedon Mar 30, 2023
Regarding #106152, does anyone know if the initialization state was explicitly excluded from the API? That was @Amanieu's concern.
tgross35 commentedon Mar 30, 2023
Yeah, it's not preferable. Just trying to see if there's any way where we could either
LazyCell<T, _>
orLazyCell<T, fn() -> T>
- so we could eventually drop the second parameter in a background-compatible way. I don't think this is possible via sealing or anything, but maybe there's a tricky way.To quote @m-ou-se in #74465 (comment)
I think that the form I suggested above with
TestLazyLock<T>
would be forward-compatible with either something like what Mara is suggesting, or with the current form (could use a sanity check here). It's not as useful as the current full featured version, but it does directly replace the purpose oflazy_static
, which is kind of the biggest target of this feature. So in theory, that could be stabilized while a more full featured version is being contemplated.tgross35 commentedon Mar 30, 2023
I am not super in the know for this, but I don't think there's any particular reason the state couldn't be exposed somehow. The state is known by the OnceCell, and would have to be tracked somehow even with a different underlying implementation.
once_cell
#74465bstrie commentedon Mar 30, 2023
I somewhat doubt it would ever be necessary to make such a change (the only edge case is rather contrived), but even then the way to drop the second parameter in a backward-compatible way would be to introduce a new API, deprecate this one, and upgrade everyone with
cargo fix
. I wouldn't stress about the existence of this parameter.122 remaining items