Skip to content

Silent failure of std::sync::Once when in const variable #132028

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

Open
CGamesPlay opened this issue Oct 22, 2024 · 8 comments · May be fixed by #132146
Open

Silent failure of std::sync::Once when in const variable #132028

CGamesPlay opened this issue Oct 22, 2024 · 8 comments · May be fixed by #132146
Labels
A-lints Area: Lints (warnings about flaws in source code) such as unused_mut. C-bug Category: This is a bug. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@CGamesPlay
Copy link

CGamesPlay commented Oct 22, 2024

I tried this code playground link:

use std::sync::Once;

const INIT: Once = Once::new();

fn main() {
    INIT.call_once(|| {
        println!("Global Once::call_once first call");
    });
    INIT.call_once(|| {
        println!("Global Once::call_once second call");
    });
}

The problem is that my Once is in a const and not a static. However, I would expect a compile error or a runtime error instead of broken behavior here. The same behavior is exhibited in std::sync::LazyLock and probably std::sync::OnceLock, due to using a Once internally.

Thanks for considering this!

Meta

Checked on 1.82.0 and on nightly.

@CGamesPlay CGamesPlay added the C-bug Category: This is a bug. label Oct 22, 2024
@rustbot rustbot added the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Oct 22, 2024
@juntyr
Copy link
Contributor

juntyr commented Oct 22, 2024

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.

@yingmanwumen
Copy link

yingmanwumen commented Oct 22, 2024

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

@juntyr
Copy link
Contributor

juntyr commented Oct 22, 2024

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!

@workingjubilee workingjubilee added A-lints Area: Lints (warnings about flaws in source code) such as unused_mut. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Oct 23, 2024
@CGamesPlay
Copy link
Author

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.

@juntyr
Copy link
Contributor

juntyr commented Oct 23, 2024

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.

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?

@CGamesPlay
Copy link
Author

CGamesPlay commented Oct 23, 2024

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.

@juntyr
Copy link
Contributor

juntyr commented Oct 23, 2024

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.

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.

@asquared31415
Copy link
Contributor

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.

@Urgau Urgau linked a pull request Oct 25, 2024 that will close this issue
@saethlin saethlin removed the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Oct 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-lints Area: Lints (warnings about flaws in source code) such as unused_mut. C-bug Category: This is a bug. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants