Skip to content

Conversation

@traviscross
Copy link
Contributor

As it turns out, the story that "the final value of a constant cannot contain any mutable references" has some nuance to it. Let's describe these caveats.

Thanks to theemathas for noticing these and to RalfJ for filling in context.

cc @RalfJung @oli-obk @theemathas @tshepang @ehuss

@rustbot rustbot added the S-waiting-on-review Status: The marked PR is awaiting review from a maintainer label Oct 22, 2025
```

> [!NOTE]
> This is allowed as, for a value of a zero-sized type, no bytes can actually be mutated. We must accept this as `&mut []` is [promoted].
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe that this is the first time in the reference that we document that mutable references to zero-length arrays can be promoted.

Note that this only works with zero-length arrays. Also, this promotion does not seem to always happen, see rust-lang/rust#140126

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this only works with zero-length arrays. Also, this promotion does not seem to always happen, see rust-lang/rust#140126

Look at the bottom note in https://doc.rust-lang.org/nightly/reference/const_eval.html#constant-expressions and rust-lang/rust#143129.

> This is allowed as it's separately not allowed to read from an external static during constant evaluation. See [const-eval.const-expr.path-static].
> [!NOTE]
> We also disallow, in the final value, shared references to mutable statics created in the initializer for a separate reason. Consider:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This preexisting note seems incorrect. It talks about mutable statics, and then shows an example with a reference to an interior-mutable temporary. Such temporaries do not live for 'static. And even if they were to, they would be a const-promoted thing, which is subtly different from statics.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In an initializer such as,

const _: &AtomicU8 = &AtomicU8::new(0);

the rules for "extending based on expressions" apply as follows:

  • The initializer is an extending expression.
  • The borrow expression is therefore an extending expression, so the operand of the borrow is an extending expression.

The "operand of an extending borrow expression has its temporary scope extended". I.e., we extend the temporary scope of AtomicU8::new(0).

How far do we extend it? "To the end of the program":

Lifetime extension also applies to static and const items, where it makes temporaries live until the end of the program.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also specify that interior mutable values are not subject to constant promotion.

https://doc.rust-lang.org/nightly/reference/destructors.html#constant-promotion

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).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is still confusing to call this a "mutable static", which one could reasonably interpret as referring to a static mut or at least some sort of static item with mutability.

Copy link
Contributor Author

@traviscross traviscross Oct 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Edit: GH didn't show me @RalfJung's comment; this was posted in parallel to it:)

All that said, I agree the wording was imperfect. I've pushed a commit to clarify the point.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is still confusing to call this a "mutable static", which one could reasonably interpret as referring to a static mut or at least some sort of static item with mutability.

Yes, agreed; this is avoided in the revision.

Copy link
Contributor

@oli-obk oli-obk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we give the failing examples a rationale, too? The passing ones have rationales, but we don't explain why e.g. we don't want to allow creating references to interior mutable data from a const unless the reference is to a static item

As it turns out, the story that "the final value of a constant cannot
contain any mutable references" has some nuance to it.  Let's describe
these caveats.

Thanks to theemathas for noticing these and to RalfJ for filling in
context.
We have a note that describes why, perhaps surprisingly, it's OK to
have a reference to a static with an interior mutable value in the
final value of a constant item but it's not OK if the interior mutable
value is a temporary from the initializer.  There was room to improve
how this was worded, so let's take it.
@traviscross traviscross force-pushed the TC/add-const-mut-exceptions branch from 2e7cb57 to 7acafcd Compare October 24, 2025 21:11
@rustbot
Copy link
Collaborator

rustbot commented Oct 24, 2025

This PR was rebased onto a different master commit. Here's a range-diff highlighting what actually changed.

Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.

@traviscross
Copy link
Contributor Author

traviscross commented Oct 24, 2025

Should we give the failing examples a rationale, too? The passing ones have rationales, but we don't explain why e.g. we don't want to allow creating references to interior mutable data from a const unless the reference is to a static item

Sure. I've added some rationales. I'm curious, though, what would you say for this one?

# #![allow(static_mut_refs)]
static mut S: u8 = 0;
const _: &dyn Send = &unsafe { &mut S }; // ERROR.
//                             ^^^^^^
// Not allowed as the mutable reference appears in the final value,
// even though type erasure occurs.

(It's easier to say, for this sort of thing, why we allow something rather than why we don't, as we're conservatively only allowing the things that we have good reason to allow and where we can prove that it's OK to allow those things.)

@traviscross traviscross force-pushed the TC/add-const-mut-exceptions branch from 7acafcd to 5cad100 Compare October 24, 2025 21:14
We had described the design rationale for why certain values of
constant items were accepted, but for the values that are not
accepted, while we had described mechanically the reasons for this, we
had not described the rationale for it.

This can be a bit tricky, because often the real rationale is that
"we're being conservative and only allowing the cases where we have a
good reason to allow them and where we can prove that allowing them is
OK".  So it is easier to describe why we allow something than why we
don't.  But still, let's try to describe some reasons why we don't
allow some things yet.
@traviscross traviscross force-pushed the TC/add-const-mut-exceptions branch from 5cad100 to 486075d Compare October 24, 2025 22:33
@oli-obk
Copy link
Contributor

oli-obk commented Oct 25, 2025

Yea that case is just a limitation of the few provenance checks happening in CTFE. We do allow

use std::sync::atomic::AtomicBool;

static S: AtomicBool = AtomicBool::new(false);
const _: &dyn Send = &unsafe { &S };

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-review Status: The marked PR is awaiting review from a maintainer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants