reference
reference copied to clipboard
allow constants to refer to mutable/external memory, but reject such constants as patterns
Reference update for https://github.com/rust-lang/rust/pull/140942.
I'm trying to figure out, which reference rule rejects the following?
static mut S: i32 = 1;
const C: &mut i32 = unsafe { &mut S }; // ERROR: constructing invalid value
What I'm getting at, is that it seems like despite removing items.const.final-value-immutable, it seems like there is still some restriction somewhere. What I'm thinking:
- final value of
&mut— never ok? - final value of
&of astatic mut— now ok - final value of
&of something with interior mutability — sometimes ok?
Is there maybe still some "final value must not have mutable references" restriction?
Is there some rule in the reference that would explain the following?
const C: &AtomicU32 = &AtomicU32::new(1); // ERROR: constants cannot refer to interior mutable data
It seems like a pretty strong blanket statement that "constants cannot refer to interior mutable data", but the AtomicU32 example from https://github.com/rust-lang/rust/pull/140942 seems like you can refer to interior mutable data. What exactly is the difference between a reference to a static versus a reference to a value created in the initializer?
Similarly, I'm trying to understand E0764. Is the description of E0764 out of date?
const C: &mut i32 = &mut 1; // ERROR: mutable references are not allowed in the final value of constants
Questions:
- Is there a rule in the reference that explains this error?
- "Mutable references (&mut) can only be used in constant functions, not statics or constants." seems to be wrong now, as you can use mutable references in statics or constants (with some limitations), right?
- "Remember: you cannot use a function call inside a constant or static"... I...don't understand this at all. The example below it clearly shows using a function call inside a constant. I can't even guess what this is trying to say.
So, I can't always tell you where any of that is in the reference, I had a hard time even find the things I am editing here.^^
But I can tell you what the compiler does:
static mut S: i32 = 1;
const C: &mut i32 = unsafe { &mut S }; // ERROR: constructing invalid value
We do a type-directed pass over the final value of the constant, mostly to check e.g. that every bool we find is 0 or 1 and things like that. As part of that pass, we just throw an error if we find any mutable reference in a const. We also error if
- we find an
UnsafeCellin memory that is immutable (see teststests/ui/statics/mutable_memory_validation.rsandtests/ui/consts/interior-mut-const-via-union.rs) in eitherconstorstatic - or if we find an
&mutpointing to immutable memory (that last case might be just UB due to an aliasing violation, or it might be const being extra paranoid, that depends on the details of the aliasing model).
const C: &AtomicU32 = &AtomicU32::new(1); // ERROR: constants cannot refer to interior mutable data
That falls out of the following two things which are in the reference:
- "shared borrows to values with interior mutability are only allowed to refer to transient places"
- and lifetime extension, which says that the
AtomicU32constructed here is not transient
Similarly, I'm trying to understand E0764. Is the description of E0764 out of date?
Oh yeah, that is very outdated. And I don't understand that "remember" thing either. (I worry this might be true for many of our error descriptions -- at least I don't ever seem them so I wouldn't even notice them being outdated. Multiple of our Exxxx tests don't even emit the right error code any more...)
const C: &mut i32 = &mut 1; // ERROR: mutable references are not allowed in the final value of constants
This is the exact same as "constants cannot refer to interior mutable data", but for a mutable reference rather than an interior mutable shared ref. I don't know why the error messages are worded so differently...
@RalfJung we had some discussion in the spec about this wording. I'm having a hard time (quite honestly) knowing with precision what you are saying and what the spec is saying. I want to present my mental model and check if it works for you and then ask you some questions in terms of that model.
CONST VALUE
A const "value" is defined by some kind of grammar that looks kind of like a valtree (but with some extra things):
- integer of some type
- a reference
&Tto a referent (defined below) - a mutable reference
&Tto a referent (defined below) - reference to a static of type
&T - mut reference to a static of type
&mut T - raw pointers, similar to the above
- a struct like
S { (f: T)* }and so forth
where a referent is either a static value (in which case we know which static) or another valtree (indicating a constant).
And then when you say, "we do a type-directed pass over the final value of the constant", I take it that you have a pair like (Type, Value) and then we can kind of "visit" the components in a relatively straightforward way. And then when you say, "we just throw an error if we find any mutable reference in a const", you mean that if at any point in this traversal, Type = &mut _ for any value of _, an error is reported?
Does that all make sense to you? Is my model accurate? If it would be helpful I can try to write it in a kind of pseudo-code-y Rust.
No, it's not a valtree. Speaking about the internal compiler representation, a ConstValue is usually actually a place (the data is stored in some internal allocation), with more efficient representations for common cases such as a value of scalar type -- but that should be just an optimization. We can't use a valtree as that would lose, for instance, padding.
But given an arbitrary place and a type, we can just walk that place according to the type. We don't construct a tree, we just do a type-guided traversal of this memory, recursing into structs and tuples and enums and so on. That's what const validation does.
Mostly that traversal just checks regular (recursive) validity, but for mutability we are super extra paranoid to make it less likely for &mut in consts to cause issues, and to ensure that even if our static (pre-const-eval) checks miss something, it gets caught later. So if these checks seem kind of arbitrary, that's because they are just as strict as we could make them while allowing all safe code.
(I will note that we are now digging into pre-existing lacks of documentation here, and have ventured far outside of what https://github.com/rust-lang/rust/pull/140942 is about. I'd rather not have to rebase that PR again while we figure out how to document things that have been undocumented for years...)
If you would, @RalfJung, have a look at the commit I pushed here with clarifications, in discussion with @ehuss. Does this look right?