Const validity checks are stricter for references to immutable memory than to mutable memory
Opened this issue · 2 comments
I'm not sure if this is a bug, or a surprising behavior caused by #140942.
#[repr(transparent)]
struct SyncPtr(*const ());
unsafe impl Sync for SyncPtr {}
static DATA: () = ();
const MAYBE_NULL: SyncPtr = SyncPtr((&raw const DATA).wrapping_byte_add(8));
static mut YES_MUT: SyncPtr = MAYBE_NULL;
static NOT_MUT: SyncPtr = MAYBE_NULL;
const WORKS: &&() = unsafe { &*(&raw const YES_MUT).cast::<&()>() };
const FAILS: &&() = unsafe { &*(&raw const NOT_MUT).cast::<&()>() };In the above code, WORKS compiles, but FAILS doesn't. I find this surprising, since, intuitively, a static should be usable in the same way as a static mut that is never mutated. The error message is below:
error[E0080]: constructing invalid value at .<deref>: encountered a null reference
--> src/lib.rs:12:1
|
12 | const FAILS: &&() = unsafe { &*(&raw const NOT_MUT).cast::<&()>() };
| ^^^^^^^^^^^^^^^^^ it is undefined behavior to use this value
|
= note: the rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior.
= note: the raw bytes of the constant (size: 8, align: 8) {
╾─────alloc5<imm>─────╼ │ ╾──────╼
}
For more information about this error, try `rustc --explain E0080`.
It seems to me that the const validity check performs checks recursively in each const, including through static allocations, but won't recurse through static mut allocations. (Although this explanation seems to contradict how #144719 behaves, where the compiler seems to recurse into a static only once the const is used in a pattern.)
Meta
Reproducible on the playground with version 1.92.0-nightly (2025-09-18 7c275d09ea6b953d2cca)
A variant that uses interior mutability instead of static mut:
use std::cell::UnsafeCell;
#[repr(transparent)]
struct UncheckedSync<T>(T);
unsafe impl<T> Sync for UncheckedSync<T> {}
static DATA: () = ();
const MAYBE_NULL: *const () = (&raw const DATA).wrapping_byte_add(8);
static YES_MUT: UncheckedSync<UnsafeCell<*const ()>> = UncheckedSync(UnsafeCell::new(MAYBE_NULL));
static NOT_MUT: UncheckedSync<*const ()> = UncheckedSync(MAYBE_NULL);
const WORKS: &&() = unsafe { &*(&raw const YES_MUT).cast::<&()>() };
const FAILS: &&() = unsafe { &*(&raw const NOT_MUT).cast::<&()>() };error[E0080]: constructing invalid value at .<deref>: encountered a null reference
--> src/lib.rs:14:1
|
14 | const FAILS: &&() = unsafe { &*(&raw const NOT_MUT).cast::<&()>() };
| ^^^^^^^^^^^^^^^^^ it is undefined behavior to use this value
|
= note: the rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior.
= note: the raw bytes of the constant (size: 8, align: 8) {
╾─────alloc6<imm>─────╼ │ ╾──────╼
}
For more information about this error, try `rustc --explain E0080`.
Another variant:
#![allow(static_mut_refs)]
static mut DATA: i32 = 0;
static mut SM: &mut i32 = unsafe { &mut DATA };
static S: &mut i32 = unsafe { &mut DATA };
const WORKS: &&mut i32 = unsafe { &SM };
const FAILS: &&mut i32 = &S;error[E0080]: constructing invalid value at .<deref>: encountered mutable reference in `const` value
--> src/lib.rs:8:1
|
8 | const FAILS: &&mut i32 = &S;
| ^^^^^^^^^^^^^^^^^^^^^^ it is undefined behavior to use this value
|
= note: the rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior.
= note: the raw bytes of the constant (size: 8, align: 8) {
╾─────alloc3<imm>─────╼ │ ╾──────╼
}
For more information about this error, try `rustc --explain E0080`.