Basically, because &mut
references are unique in rust, mutations (except through interior mutability) are not actually observable. Therefore, const fn
can support mutation. i.e.:
&mut
arguments could be accepted&mut
expressions)x = y;
or *x = y;
).The const_mut_refs
feature gate allows all of the above in const fn
, but not in other const items. Allowing mutable references outside of const fn
is tracked in https://github.com/rust-lang/rust/issues/71212
Thanks for filing this issue. :)
- non-initialization assignments could be made (
x = y;
or*x = y;
).
The first half of this, i.e. x = y
, is covered by https://github.com/rust-lang/rust/issues/48821 which is being stabilized in https://github.com/rust-lang/rust/pull/57175.
I could be wrong, but based on previous conversation with @eddyb, this is very much in the plans. They originally wanted to even allow CTFE to mutate statics during the scope of their execution, until it was discussed out that this is undesirable because it would allow a single function call to behave differently depending on whether or not CTFE was used to precompute the function.
Please cc @oli-obk and/or @RalfJung on all CTFE-related things.
In terms of the implementation, we plan to allow all pointers where the memory they point to is on the CTFE "stack" (i.e. not a static) and maybe "heap" in the future, with maybe some limitations around const fn
itself.
But miri can already interpret more than everything you'd reasonably want to run in CTFE.
I think this is really useful.
i.e. pre-seeding a hash function with a context specific data:
pub const fn context_hash(context: &'static [u8]) -> Sha256 {
let mut h = Sha256::new();
h.input(context);
let res = h.result_reset();
h.input(&res);
h.input(&res);
h
}
This can give a good performance boost in some applications(don't need to do the context hashing every time you hash something)
Generally, const function must not have side effects, function to stay pure(what equals) must:
&mut to something can be used as usize for arithmetic, so in this case it will not cause side effect.
In practice, we can allow some cases, where we can mutate some constant at time of evaluating another, the problem is order of evaluating constants as well as syntax like const mut
(To mark mutable things inside const context) and co (we won't violate mutability rules, right?).
Const function with side effect will be much more difficult to implement, and, moreover, syntax and ordering is not clear.
Should this issue be blocking const EMPTY_MAX: &'static mut [()] = &mut [(); ::std::usize::MAX]
? Currently it does, but I think we should be able to allow this (as well as = &mut [];
for a slice of any type).
@cramertj That (&'static mut [T; 0]
) is a silly artificial limitation AFAIK, with a FIXME in the code, we just never fixed it (it's limited to runtime fn
s, IIRC) - feel free to submit a PR and maybe we can FCP that or w/e. This issue is about non-'static
&mut
in CTFE.
&'static mut T
where T
is a ZST is an orthogonal extension of that, which we even used to have to some extent, but we got rid of it when we RFC'd/stabilized rvalue-to-'static
promotion.
I'm not sure we have any issue for it, maybe you can create one? It would also help to provide realistic examples for ZST expressions you want to get a &'static mut
out of (e.g. [(); n]
vs [Struct {...}; n]
vs [Foo::new(); n]
- the last one might need to use actual layout_of
which may be more controversial).
[Foo::new(); n]
No please no function calls in implicit promotion.
Ah fair, I was thinking more from the fact that the value doesn't really matter if it's a ZST, but if there are preconditions (e.g. assertions) they would need to run first anyway.
I'm nominating this for @rust-lang/lang consideration for stabilization. This feature gate allows &mut
references in arguments, return values and local variables in const fn
. They are not permitted in any other const context, so you can't use mutable references in const items, static items, array length initializers, const generics or enum discriminant initializers. The reason is that we aren't sure how to enforce that no mutable references escape to the final value of the constant. With const fn
there's no danger of this, because the borrow checker forbids returning mutable references to local variables. Any mutable references in the arguments that could escape to the return type must by construction be created within another const fn, as they can't even be created in any other context.
Examples:
const fn foo<'a>(x: &'a mut i32) -> &'a mut i32 {
let mut y = 42;
let z = &mut y;
*z = 99;
x
}
is legal, but
const FOO: () = {
let mut y = 42;
let z = &mut y;
*z = 99;
};
is not, because we want to prevent
const FOO: &mut i32 = &mut 42;
and the easiest way to do this is to forbid mutable references. #71212 is tracking adding mutable references to const contexts other than const fn
again, but that's actually not terribly important compared with all the use cases for mutable references in const fn
.
Said uses cases being several of the issues that link to this issue (scroll up) and more concretely:
MaybeUninit::zeroed
(https://github.com/rust-lang/rust/issues/62061)std::mem::replace
There are no open questions known to me (beyond the use of &mut
outside of const fn) and the implementation in the miri engine is heavily tested in the miri tool since the early days of miri.
cc @rust-lang/wg-const-eval
You can use mutable references in const items with just this by putting your expression into a const function and then calling the function. I even made a macro_rules macro that can do it for you (playground)
macro_rules! mut_in_const_item {
(const $id:ident: $ty:ty = $e:expr) => {
const $id: $ty = {
const fn __mut_in_const_item() -> $ty {
$e
}
__mut_in_const_item()
};
}
}
Is there a reason the compiler can't do this for other const
contexts? Should this question be asked in a different place?
Should this question be asked in a different place?
yes :laughing: in https://github.com/rust-lang/rust/issues/71212
We can do an analysis that figures out that using &mut
is fine, we just need to implement it, #71212 tracks that.
I have two questions:
Are there other cases where things are allowed in const fn but not other consts right now?
No, there is no precedent for this. We have other differences between different const contexts though (e.g. constants cannot even mention statics, but statics can mention other statics).
- Can you estimate the work required for #71212?
@ecstatic-morse is the expert on dataflow, maybe they know what needs to be done to exclude any mutable references in the final expression of a constant. We're already forbidding
const FOO: &i32 = {
let x = 42;
&x
};
so all we need to figure out is how to forbid
const FOO: &i32 = {
let x = 2;
&mut (1 + x)
};
because
const FOO: &i32 = {
let x = 2;
&(1 + x)
};
is ok.
Why is it a problem if the type of the constant contains no mutable references (as in the example you wish to forbid)?
The mutability of the thing could be hidden. Take for example a user-written wrapper that emulates mutable references:
pub struct MutRef<T>(*mut T);
impl<T> MutRef<T> {
pub const fn new(x: &'static mut T) -> Self {
Self(x)
}
pub fn into_mut_ref(self) -> &'static mut T {
unsafe {
&mut *self.0
}
}
}
This would allow you to create a
const FOO: MutRef<i32> = MutRef::new(&mut 42);
and then call FOO.into_mut_ref()
, because constants aren't moved out of, they are implicitly copied even if they are !Copy
.
Unnominating, let's give mutable references outside const fn another try. I'll post an idea in #71212
Sometime between nightly-2020-06-03
and nightly-2020-10-25
the compiler changed such that code that previously worked with only a const_fn
feature now also requires const_mut_refs
(see https://github.com/tock/tock/pull/2175). A minimal example is here:
pub struct Foo {
bar: Cell<Option<&'static mut u32>>,
}
impl Foo {
pub const fn new() -> Self {
Self {
bar: Cell::new(None)
}
}
}
This limitation feels really artificial, because there is not actually a mutable reference being used in const
context. I assume it is just difficult to implement the const-mutable-reference-checking in such a way that this is allowable?
Sometime between nightly-2020-06-03 and nightly-2020-10-25 the compiler changed such that code that previously worked with only a const_fn feature now also requires const_mut_refs (see tock/tock#2175)
Yes, there is work going on towards removing the general const_fn
feature and having more specific sub-features instead. In fact, just the const_mut_refs
feature should be enough.
So, there is not really a new restriction; this is just the old restriction under a more specific name.
Adding to that, the limitation is indeed artificial, because we rather implemented a conservative check than accidentally allow something unsound. We have just recently made some changes that should allow us to try to tackle mutable references in constant items again.
Most helpful comment
I'm nominating this for @rust-lang/lang consideration for stabilization. This feature gate allows
&mut
references in arguments, return values and local variables inconst fn
. They are not permitted in any other const context, so you can't use mutable references in const items, static items, array length initializers, const generics or enum discriminant initializers. The reason is that we aren't sure how to enforce that no mutable references escape to the final value of the constant. Withconst fn
there's no danger of this, because the borrow checker forbids returning mutable references to local variables. Any mutable references in the arguments that could escape to the return type must by construction be created within another const fn, as they can't even be created in any other context.Examples:
is legal, but
is not, because we want to prevent
and the easiest way to do this is to forbid mutable references. #71212 is tracking adding mutable references to const contexts other than
const fn
again, but that's actually not terribly important compared with all the use cases for mutable references inconst fn
.Said uses cases being several of the issues that link to this issue (scroll up) and more concretely:
MaybeUninit::zeroed
(https://github.com/rust-lang/rust/issues/62061)std::mem::replace
There are no open questions known to me (beyond the use of
&mut
outside of const fn) and the implementation in the miri engine is heavily tested in the miri tool since the early days of miri.cc @rust-lang/wg-const-eval