lang (typesystem | borrowck | machine | const-eval | value-promotion)
Promote constexpr rvalues to values in static memory instead of
stack slots, and expose those in the language by being able to directly create
'static
references to them. This would allow code like
let x: &'static u32 = &42
to work.
Right now, when dealing with constant values, you have to explicitly define
const
or static
items to create references with 'static
lifetime,
which can be unnecessarily verbose if those items never get exposed
in the actual API:
fn return_x_or_a_default(x: Option<&u32>) -> &u32 {
if let Some(x) = x {
x
} else {
static DEFAULT_X: u32 = 42;
&DEFAULT_X
}
}
fn return_binop() -> &'static Fn(u32, u32) -> u32 {
const STATIC_TRAIT_OBJECT: &'static Fn(u32, u32) -> u32
= &|x, y| x + y;
STATIC_TRAIT_OBJECT
}
This workaround also has the limitation of not being able to refer to type parameters of a containing generic functions, eg you can't do this:
fn generic<T>() -> &'static Option<T> {
const X: &'static Option<T> = &None::<T>;
X
}
However, the compiler already special cases a small subset of rvalue const expressions to have static lifetime - namely the empty array expression:
let x: &'static [u8] = &[];
And though they don't have to be seen as such, string literals could be regarded as the same kind of special sugar:
let b: &'static [u8; 4] = b"test";
// could be seen as `= &[116, 101, 115, 116]`
let s: &'static str = "foo";
// could be seen as `= &str([102, 111, 111])`
// given `struct str([u8]);` and the ability to construct compound
// DST structs directly
With the proposed change, those special cases would instead become part of a general language feature usable for custom code.
Inside a function body's block:
&<constexpr>
)UnsafeCell { ... }
constructor.UnsafeCell
.'static
lifetime.The UnsafeCell
restrictions are there to ensure that the promoted value is
truly immutable behind the reference.
Examples:
// OK:
let a: &'static u32 = &32;
let b: &'static Option<UnsafeCell<u32>> = &None;
let c: &'static Fn() -> u32 = &|| 42;
let h: &'static u32 = &(32 + 64);
fn generic<T>() -> &'static Option<T> {
&None::<T>
}
// BAD:
let f: &'static Option<UnsafeCell<u32>> = &Some(UnsafeCell { data: 32 });
let g: &'static Cell<u32> = &Cell::new(); // assuming conf fn new()
These rules above should be consistent with the existing rvalue promotions in const
initializer expressions:
// If this compiles:
const X: &'static T = &<constexpr foo>;
// Then this should compile as well:
let x: &'static T = &<constexpr foo>;
The necessary changes in the compiler did already get implemented as part of codegen optimizations (emitting references-to or memcopies-from values in static memory instead of embedding them in the code).
All that is left do do is "throw the switch" for the new lifetime semantic by removing these lines: https://github.com/rust-lang/rust/blob/29ea4eef9fa6e36f40bc1f31eb1e56bf5941ee72/src/librustc/middle/mem_categorization.rs#L801-L807
(And of course fixing any fallout/bitrot that might have happened, adding tests, etc.)
One more feature with seemingly ad-hoc rules to complicate the language...
It would be possible to extend support to &'static mut
references,
as long as there is the additional constraint that the
referenced type is zero sized.
This again has precedence in the array reference constructor:
// valid code today
let y: &'static mut [u8] = &mut [];
The rules would be similar:
&mut <constexpr>
)UnsafeCell { ... }
constructor.UnsafeCell
.'static
lifetime.The zero-sized restriction is there because aliasing mutable references are only safe for zero sized types (since you never dereference the pointer for them).
Example:
fn return_fn_mut_or_default(&mut self) -> &FnMut(u32, u32) -> u32 {
self.operator.unwrap_or(&mut |x, y| x * y)
// ^ would be okay, since it would be translated like this:
// const STATIC_TRAIT_OBJECT: &'static mut FnMut(u32, u32) -> u32
// = &mut |x, y| x * y;
// self.operator.unwrap_or(STATIC_TRAIT_OBJECT)
}
let d: &'static mut () = &mut ();
let e: &'static mut Fn() -> u32 = &mut || 42;
There are two ways this could be taken further with zero-sized types:
UnsafeCell
restriction if the type of the rvalue is zero-sized.Both cases would work because one can't cause memory unsafety with a reference to a zero sized value, and they would allow more safe code to compile.
However, they might complicated reasoning about the rules more, especially with the last one also being possibly confusing in regards to side-effects.
Not doing this means:
static
and const
items to create 'static
references, which won't work in generics.&'static mut
references to zero-sized
types, though that part could also be achieved by allowing mutable references to
zero-sized types in constants.None, beyond "Should we do alternative 1 instead?".