const CONST: AtomicIsize = AtomicIsize::new(0);
// ^ compiles, storing in one place doesn't affect loading elsewhere
static STATIC: AtomicIsize = AtomicIsize::new(0);
// ^ compiles, value stored in one place can be loaded elsewhere
const CONST_REF: &AtomicIsize = &AtomicIsize::new(0);
// ^ error[E0492]: constants cannot refer to interior mutable data
// makes sense
static STATIC_REF: &AtomicIsize = &AtomicIsize::new(0);
// ^ error[E0492]: statics cannot refer to interior mutable data
// why?
Note that constants in Rust aren't stored anywhere, but rather they are evaluated at compile time and the result is copied to the locations the constant is used. If I were to refer to CONST multiple times, all invocations would result in different instances of AtomicIsize. Static variables on the other hand do have a single location in memory that is referred to whenever the static variable is used somewhere.
I know, and that's exactly why I don't understand the reason of statics not being allowed to directly refer to interior mutable data,but is somehow allowed to indirectly do so.
I believe STATIC_REF doesn't compile, because it essentially creates CONST_REF first, saving it to a global memory location referred to by the static variable.
It creates a reference to a global variable, not a reference to a constant with interior mutability. STATIC creates a constant AtomicIsize, which is fine since it is not borrowed, unlike &AtomicIsize.
I assume static variables don't get that kind of extra treatment because the added complexity during compilation wouldn't be worth the effort? But I'm not a compiler engineer. In my mental model of static variables in Rust, they are just constants expressions with a single location in memory. So all the restrictions of const evaluation apply to them. I personally would find it a lot weirder if
Promotion of a value expression to a 'static slot occurs when the expression could be written in a constant and borrowed, and that borrow could be dereferenced where the expression was originally written, without changing the runtime behavior. That is, the promoted expression can be evaluated at compile-time and the resulting value does not contain interior mutability or destructors (these properties are determined based on the value where possible, e.g. &None always has the type &'static Option<_>, as it contains nothing disallowed).
when reading through the information provided by
I can understand why values with destructors are excluded by the promotion rule (in non-'static scopes, destructors are not expected to never be excecuted), but I still don't understand why values with interior mutability are designed to be excluded.
If the reference has type &Cell<i32> it is quite clear that the program can easily observe whether two references point to the same memory even without comparing their address: Changes through one reference will affect reads through the other. So, we cannot allow constant references to types that have interior mutability (types that are not Freeze )...
src: const-eval/const.md at master · rust-lang/const-eval · GitHub
But these are reasons for const items. Const item needs the value to be truely immutable anywhere.
i.e. Promoted &AtomicIsize is not allowed in statics.
i.e. Making the Atomic value static, or reference-to-static is allowed:
use core::sync::atomic::AtomicIsize;
static S: AtomicIsize = AtomicIsize::new(0);
static A: &AtomicIsize = &S; // compiles: not promotion, just refer to a static
// static S: &AtomicIsize = &AtomicIsize::new(0); // fail: promotion
Aside: Promotion means
// if this compiles
const X: &'static T = &CONST_EXPR;
// then this compiles
let x: &'static T = &CONST_EXPR;
To make static S: &AtomicIsize = &AtomicIsize::new(0) work, the compiler tries to treat the reference as a static promotion instead of a reference to a static. This is reasonable: AtomicIsize::new(0) is not an explicit static item, so rustc will by default treat it as a constant value
static S: &AtomicIsize = &AtomicIsize::new(0); // fail: promotion
static S: &AtomicIsize = &const { AtomicIsize::new(0) }; // rustc may treat like this
static S: &AtomicIsize = { const A: AtomicIsize = AtomicIsize::new(0); &A }; // rustc may treat like this
static S: &AtomicIsize = { static A: AtomicIsize = AtomicIsize::new(0); &A }; // not this
So maybe compiler hint could be improved to advise for an intermediate static? (My fault: I was too preoccupied by the misleading error message that I failed to notice the advice was already there) Saying it doesn't work because statics cannot refer to interior mutable data is misleading.
Rust compiler doesn't exist in vacuum. It's goal is to produce some kind of binary that you may actually run.
And it wants to avoid “life before main”.
And that means static should be something that linker may produce.
And what does linker produce on most common platforms? That's simple: precomputed sequence of bytes plus relocations.
What are relocations? Most relocations on most common platforms are designed to massage code into something runnable, but because linkers are designed to support C they also have one common relocation designed to work with globals: address of [global] variable is added the isize data you supply (so efficiently it's address of global plus fixed offset).
And AFAICS this limitation is to both make compiler simpler and also to avoid code like this:
Const fns that are not marked #[rustc_promotable] are not promotable.
#[rustc_promotable] can't be applied outside of compiler or standard library, so basically, all custom const fns are not promotable.
And the only way to make lifetime extension to the result of them is by wrapping it in const /static initializers.
As a consequence, we only promote code that can never fail to evaluate (see RFC 3027). This ensures that even if promotion happens inside dead code, this will not turn a "runtime error in dead code" (which is not an error at all) into a compile-time error. In particular, we cannot promote calls to arbitrary const fn, as discussed in detail in rust-lang/const-eval#19. Thus, only functions marked #[rustc_promotable] are promotable.
There is one exception to this rule: the bodies of const/static initializers. This code is never compiled, so we do not actually have to evaluate constants that occur in dead code. If we are careful enough during compilation, we can ensure that only constants whose value is actually needed are evaluated. We thus can be more relaxed about promotion; in practice, what this means is that we will promote calls to arbitrary const fn, not just those marked #[rustc_promotable].
See below for another special case in promotion analysis: accesses and references to statics are only promoted inside other statics.