Change:
Consider the following code: This should just work. But instead it throws an error: The reason for this is that gets lifetime extended via promotion, so this reference now points to a separate mutable static -- but mutable statics cannot be mutated during CTFE. I see no way to fix this, other than stopping to promote mutable references -- which we should IMO do anyway, they are a very strange and unprincipled special case. Just imagine doing this in a loop, suddenly all these different and mutable allocations share the same address... Cc
Ugh, that's an ancient rule (believe it or not, it's a workaround for not being able to write and have the length be inferred!) and it was never meant to get this far, I guess I didn't think it through properly when adding the "outermost scope" rule, which is the only position where allowing it makes sense. In the current implementation, it should've never been promoted, only allowed, my bad!

Discussion:
// In theory, any zero-sized value could be borrowed // mutably without consequences. However, only &mut [] // is allowed right now, and only in functions. <del> if self.const_kind == Some(hir::ConstContext::Static(hir::Mutability::Mut)) { // Inside a `static mut`, &mut [...] is also allowed. match ty.kind() { ty::Array(..) | ty::Slice(_) => {} _ => return Err(Unpromotable), } } else if let ty::Array(_, len) = ty.kind() { </del> <ins> if let ty::Array(_, len) = ty.kind() { </ins> // FIXME(eddyb) the `self.is_non_const_fn` condition // seems unnecessary, given that this is merely a ZST. match len.try_eval_usize(self.tcx, self.param_env) {

// In theory, any zero-sized value could be borrowed // mutably without consequences. However, only &mut [] // is allowed right now, and only in functions. <del> if self.const_kind == Some(hir::ConstContext::Static(hir::Mutability::Mut)) { // Inside a `static mut`, &mut [...] is also allowed. match ty.kind() { ty::Array(..) | ty::Slice(_) => {} _ => return Err(Unpromotable), } } else if let ty::Array(_, len) = ty.kind() { </del> <ins> if let ty::Array(_, len) = ty.kind() { </ins> // FIXME(eddyb): We only return `Unpromotable` for `&mut []` inside a // const context which seems unnecessary given that this is merely a ZST. match len.try_eval_usize(self.tcx, self.param_env) {

<ins> // ignore-tidy-linelength // We do not promote mutable references. static mut TEST1: Option<&mut [i32]> = Some(&mut [1, 2, 3]); //~ ERROR temporary value dropped while borrowed static mut TEST2: &'static mut [i32] = { let x = &mut [1,2,3]; //~ ERROR temporary value dropped while borrowed x }; fn main() {} </ins>

<ins> error[E0716]: temporary value dropped while borrowed --> $DIR/promote-no-mut.rs:3:50 | LL | static mut TEST1: Option<&mut [i32]> = Some(&mut [1, 2, 3]); |                                        ----------^^^^^^^^^- |                                        |         |        | |                                        |         |        temporary value is freed at the end of this statement |                                        |         creates a temporary which is freed while still in use |                                        using this value as a static requires that borrow lasts for `'static` error[E0716]: temporary value dropped while borrowed --> $DIR/promote-no-mut.rs:6:18 | LL |     let x = &mut [1,2,3]; |                  ^^^^^^^ creates a temporary which is freed while still in use LL |     x |     - using this value as a static requires that borrow lasts for `'static` LL | }; | - temporary value is freed at the end of this statement error: aborting due to 2 previous errors For more information about this error, try `rustc --explain E0716`. </ins>

<ins> // run-pass #![feature(const_mut_refs)] static mut TEST: i32 = { // We must not promote this, as CTFE needs to be able to mutate it later. let x = &mut [1,2,3]; x[0] += 1; x[0] }; // This still works -- it's not done via promotion. #[allow(unused)] static mut TEST2: &'static mut [i32] = &mut [0,1,2]; fn main() { assert_eq!(unsafe { TEST }, 2); } </ins>