Closed CAD97 closed 2 years ago
On the other hand, perhaps this is an advantage; initialization is #[cold] and only called once, so inlining the initialization codepath may serve more to make inlining callers more costly than to make initialization less expensive.
Initialization is also dyn
, so some devirtualization has to happen:
Another problem with the macro is that introduces a fresh type per lazy instance, (which also leaks into the API).
So, I don't think this shold be included in the library, or in std.
But it might be a good fit for the recipes section of the docs!
https://github.com/matklad/once_cell/blob/master/src/lib.rs#L27
I agree with matklad
here. A Lazy
storing anything aside from a fn() -> T
is a code smell that indicates it should be replaced with a OnceCell
.
I think I'm in agreement: the patterns implemented by these macros can be documented in the docs and that's sufficient to close this "issue." Providing them as macros is not really necessary (especially given the reliance on TAIT for niceness; having static GLOBAL: impl Lazy<Resource>;
I think is fine, whereas a custom type is significantly heavier and less nice).
A
Lazy
storing anything aside from afn() -> T
is a code smell that indicates it should be replaced
Is this justification for removing the second generic on Lazy
? Doing so for once_cell
is probably just breaking for minimal gain, but doing so for std
's unstable version might be beneficial.
Note that a simple let it = Lazy::new(make);
will use the fn()->T {make}
ZST rather than the erased fn() -> T
function pointer.
Rustc is smart enough to say expected `fn() -> T` found `T`
instead of expected `Lazy<T, fn() -> T>` found `Lazy<T, T>`
when calling Lazy::new(T)
, so perhaps there's no reason to prevent the fully inferred case from using dataful closures and monomorphized ZST function types.
with a
OnceCell
.
This isn't always possible (without just rewriting Lazy
); consider:
let big_data = BigData::acquire();
let lazy = LazyCell::new(|| process(big_data));
if maybe() {
observe(&*lazy);
}
if maybe() {
observe(&*lazy);
}
The only way to rewrite that with OnceCell
or Lazy<_, fn() -> _>
is to change semantics (only acquire BigData
inside the lazy callback) or reinvent Lazy
by mem::take
ing big_data
into the closure, since it can't be FnOnce
in multiple arms.
A Lazy storing anything aside from a fn() -> T is a code smell
Yeah, I think I wouldn't agree with that -- it is sometimes (but rarely) useful to use a Lazy local variable (which is exactly CAD97's example)
Closing! This is a useful discussion, but I think conclusion is that we don't actually want to change anything!
The main disadvantage of the
Lazy
type overlazy_static!
is thatLazy<T>
necessarily stores and callsfn() -> T
rather thanimpl FnOnce() -> T
. This means that any inlining of the initialization must happen via devirtualization, rather than coming for free with monomorphization.On the other hand, perhaps this is an advantage; initialization is
#[cold]
and only called once, so inlining the initialization codepath may serve more to make inlining callers more costly than to make initialization less expensive.What doesn't suffer from the (perceived?) virtualization cost is the
fn get_global() -> &'static Global
pattern usingOnceCell
directly rather than throughLazy
. Using a macro interface, we can make this pattern more accessible.Presented for consideration, a potential implementation:
I understand that part of the draw of
once_cell
is the macroless API, but providing optional macros with straightforward documented translation[^1] can help as a documentation point for common-to-the-point-of-generalization patterns for using the API.[^1]: Straightforward in the expository documentation, even if the actual expansion is more complicated.
macro_rules!
always have to do interesting things to achieve proper hygiene when defining items.It probably makes more sense to keep the simple API for the time being (e.g. because the nice impls I provide rely on TAIT in order to be properly nice), but if/when std's version of
OnceCell
is available, it might make sense to wrap some common patterns into simple macros in this (or another) crate.