Open CGamesPlay opened 3 weeks ago
This code is behaving correctly but unintuitively. A const value behaves "as-if" it is copied in at every usage point, so each call gets a fresh Once to work with.
I agree that this is a potential papercut. Ideally, there would be a clippy lint to detect any std::sync or std::cell type in a const, since it a static may have been intended instead.
Ideally, there would be a clippy lint to detect any std::sync or std::cell type in a const...
@juntyr
There has been a clippy rule named 'declare_interior_mutable_const' that would give a warning in this situation: a const item should not be interior mutable, consider making this a static item
.
However, the compiler itself doesn't deal with it for it doesn't break any basic grammars or semantic rules... Though I agree that a const sync
type item is counter-intuitive and might behave unexpectly, it still worths considering that if the compiler should capture it as a compiling warning/error. @CGamesPlay
There has been a clippy rule named 'declare_interior_mutable_const' that would give a warning in this situation:
a const item should not be interior mutable, consider making this a static item
.
That’s great, thank you for pointing out that it already exists!
Didn't know about the clippy lint, it definitely would have immediately indicated this problem to me.
So, is the fact that LazyLock
accepts an FnOnce
which is called more than one time not a safety issue? I honestly don't know, but in any case it's a lie to the library consumer.
So, is the fact that
LazyLock
accepts anFnOnce
which is called more than one time not a safety issue? I honestly don't know, but in any case it's a lie to the library consumer.
The FnOnce is technically only called once. The issue is that because you are using a const, every time you name it (in your two calls), it’s as if you had pasted the const initialisation code to this usage to construct a fresh LazyLock with a fresh FnOnce that is then only called this once. On the next usage of the const, you are referring to a completely unrelated LazyLock with a completely unrelated FnOnce.
Again, this is unintuitive but the fully correct behaviour of consts, since they by definition have no state that can change at runtime.
Perhaps the docs of these types could have an extra warning to call out this papercut specifically?
Yeah... it definitely feels bad. The explanation makes sense, and leaves me with the question, "why would anyone want to do this?"
It seems to me that anyone using LazyLock
, Once
, etc in a const
is very likely creating a bug. Perhaps a solution would be to add an attribute like must_use
, but for non_const
? Then applying that attribute to LazyLock
would result in a compiler warning when a LazyLock
is assigned to a const.
[append]
For reference, clippy's borrow_interior_mutable_const is certainly useful here, but is warn
by default, presumably because it can't be 100% certain that the issue is a bug (see "known issues"). So annotating the type may be necessary.
Yeah... it definitely feels bad. The explanation makes sense, and leaves me with the question, "why would anyone want to do this?"
It seems to me that anyone using
LazyLock
,Once
, etc in aconst
is very likely creating a bug. Perhaps a solution would be to add an attribute likemust_use
, but fornon_const
? Then applying that attribute toLazyLock
would result in a compiler warning when aLazyLock
is assigned to a const.
Agreed. I’ve personally used them in consts but only for type level trickery where I was absolutely sure of what I was doing. So making the lint a bit more on the nose (e.g. by up streaming it from clippy to rustc) would likely avoid many logic bugs while not being too annoying in the few cases where we do need locks and cells in consts.
The rustc warn by default const_item_mutation
lint might be useful to extend to trigger on all types that may contain interior mutability? This would be a problem for Cell and other interior mutability constructs too.
I tried this code playground link:
The problem is that my Once is in a
const
and not astatic
. However, I would expect a compile error or a runtime error instead of broken behavior here. The same behavior is exhibited instd::sync::LazyLock
and probablystd::sync::OnceLock
, due to using a Once internally.Thanks for considering this!
Meta
Checked on 1.82.0 and on nightly.