|
| 1 | +- Feature Name: rvalue_static_promotion |
| 2 | +- Start Date: 2015-12-18 |
| 3 | +- RFC PR: [#1414](https://github.com/rust-lang/rfcs/pull/1414) |
| 4 | +- Rust Issue: [#38865](https://github.com/rust-lang/rust/issues/38865) |
| 5 | + |
| 6 | +# Summary |
| 7 | +[summary]: #summary |
| 8 | + |
| 9 | +Promote constexpr rvalues to values in static memory instead of |
| 10 | +stack slots, and expose those in the language by being able to directly create |
| 11 | +`'static` references to them. This would allow code like |
| 12 | +`let x: &'static u32 = &42` to work. |
| 13 | + |
| 14 | +# Motivation |
| 15 | +[motivation]: #motivation |
| 16 | + |
| 17 | +Right now, when dealing with constant values, you have to explicitly define |
| 18 | +`const` or `static` items to create references with `'static` lifetime, |
| 19 | +which can be unnecessarily verbose if those items never get exposed |
| 20 | +in the actual API: |
| 21 | + |
| 22 | +```rust |
| 23 | +fn return_x_or_a_default(x: Option<&u32>) -> &u32 { |
| 24 | + if let Some(x) = x { |
| 25 | + x |
| 26 | + } else { |
| 27 | + static DEFAULT_X: u32 = 42; |
| 28 | + &DEFAULT_X |
| 29 | + } |
| 30 | +} |
| 31 | +fn return_binop() -> &'static Fn(u32, u32) -> u32 { |
| 32 | + const STATIC_TRAIT_OBJECT: &'static Fn(u32, u32) -> u32 |
| 33 | + = &|x, y| x + y; |
| 34 | + STATIC_TRAIT_OBJECT |
| 35 | +} |
| 36 | +``` |
| 37 | + |
| 38 | +This workaround also has the limitation of not being able to refer to |
| 39 | +type parameters of a containing generic functions, eg you can't do this: |
| 40 | + |
| 41 | +```rust |
| 42 | +fn generic<T>() -> &'static Option<T> { |
| 43 | + const X: &'static Option<T> = &None::<T>; |
| 44 | + X |
| 45 | +} |
| 46 | +``` |
| 47 | + |
| 48 | +However, the compiler already special cases a small subset of rvalue |
| 49 | +const expressions to have static lifetime - namely the empty array expression: |
| 50 | + |
| 51 | +```rust |
| 52 | +let x: &'static [u8] = &[]; |
| 53 | +``` |
| 54 | + |
| 55 | +And though they don't have to be seen as such, string literals could be regarded |
| 56 | +as the same kind of special sugar: |
| 57 | + |
| 58 | +```rust |
| 59 | +let b: &'static [u8; 4] = b"test"; |
| 60 | +// could be seen as `= &[116, 101, 115, 116]` |
| 61 | + |
| 62 | +let s: &'static str = "foo"; |
| 63 | +// could be seen as `= &str([102, 111, 111])` |
| 64 | +// given `struct str([u8]);` and the ability to construct compound |
| 65 | +// DST structs directly |
| 66 | +``` |
| 67 | + |
| 68 | +With the proposed change, those special cases would instead become |
| 69 | +part of a general language feature usable for custom code. |
| 70 | + |
| 71 | +# Detailed design |
| 72 | +[design]: #detailed-design |
| 73 | + |
| 74 | +Inside a function body's block: |
| 75 | + |
| 76 | +- If a shared reference to a constexpr rvalue is taken. (`&<constexpr>`) |
| 77 | +- And the constexpr does not contain a `UnsafeCell { ... }` constructor. |
| 78 | +- And the constexpr does not contain a const fn call returning a type containing a `UnsafeCell`. |
| 79 | +- Then instead of translating the value into a stack slot, translate |
| 80 | + it into a static memory location and give the resulting reference a |
| 81 | + `'static` lifetime. |
| 82 | + |
| 83 | +The `UnsafeCell` restrictions are there to ensure that the promoted value is |
| 84 | +truly immutable behind the reference. |
| 85 | + |
| 86 | +Examples: |
| 87 | + |
| 88 | +```rust |
| 89 | +// OK: |
| 90 | +let a: &'static u32 = &32; |
| 91 | +let b: &'static Option<UnsafeCell<u32>> = &None; |
| 92 | +let c: &'static Fn() -> u32 = &|| 42; |
| 93 | + |
| 94 | +let h: &'static u32 = &(32 + 64); |
| 95 | + |
| 96 | +fn generic<T>() -> &'static Option<T> { |
| 97 | + &None::<T> |
| 98 | +} |
| 99 | + |
| 100 | +// BAD: |
| 101 | +let f: &'static Option<UnsafeCell<u32>> = &Some(UnsafeCell { data: 32 }); |
| 102 | +let g: &'static Cell<u32> = &Cell::new(); // assuming conf fn new() |
| 103 | +``` |
| 104 | + |
| 105 | +These rules above should be consistent with the existing rvalue promotions in `const` |
| 106 | +initializer expressions: |
| 107 | + |
| 108 | +```rust |
| 109 | +// If this compiles: |
| 110 | +const X: &'static T = &<constexpr foo>; |
| 111 | + |
| 112 | +// Then this should compile as well: |
| 113 | +let x: &'static T = &<constexpr foo>; |
| 114 | +``` |
| 115 | + |
| 116 | +## Implementation |
| 117 | + |
| 118 | +The necessary changes in the compiler did already get implemented as |
| 119 | +part of codegen optimizations (emitting references-to or memcopies-from values in static memory instead of embedding them in the code). |
| 120 | + |
| 121 | +All that is left do do is "throw the switch" for the new lifetime semantic |
| 122 | +by removing these lines: |
| 123 | +https://github.com/rust-lang/rust/blob/29ea4eef9fa6e36f40bc1f31eb1e56bf5941ee72/src/librustc/middle/mem_categorization.rs#L801-L807 |
| 124 | + |
| 125 | +(And of course fixing any fallout/bitrot that might have happened, adding tests, etc.) |
| 126 | + |
| 127 | +# Drawbacks |
| 128 | +[drawbacks]: #drawbacks |
| 129 | + |
| 130 | +One more feature with seemingly ad-hoc rules to complicate the language... |
| 131 | + |
| 132 | +# Alternatives, Extensions |
| 133 | +[alternatives]: #alternatives |
| 134 | + |
| 135 | +It would be possible to extend support to `&'static mut` references, |
| 136 | +as long as there is the additional constraint that the |
| 137 | +referenced type is zero sized. |
| 138 | + |
| 139 | +This again has precedence in the array reference constructor: |
| 140 | + |
| 141 | +```rust |
| 142 | +// valid code today |
| 143 | +let y: &'static mut [u8] = &mut []; |
| 144 | +``` |
| 145 | + |
| 146 | +The rules would be similar: |
| 147 | + |
| 148 | +- If a mutable reference to a constexpr rvalue is taken. (`&mut <constexpr>`) |
| 149 | +- And the constexpr does not contain a `UnsafeCell { ... }` constructor. |
| 150 | +- And the constexpr does not contain a const fn call returning a type containing a `UnsafeCell`. |
| 151 | +- _And the type of the rvalue is zero-sized._ |
| 152 | +- Then instead of translating the value into a stack slot, translate |
| 153 | + it into a static memory location and give the resulting reference a |
| 154 | + `'static` lifetime. |
| 155 | + |
| 156 | +The zero-sized restriction is there because |
| 157 | +aliasing mutable references are only safe for zero sized types |
| 158 | +(since you never dereference the pointer for them). |
| 159 | + |
| 160 | +Example: |
| 161 | + |
| 162 | +```rust |
| 163 | +fn return_fn_mut_or_default(&mut self) -> &FnMut(u32, u32) -> u32 { |
| 164 | + self.operator.unwrap_or(&mut |x, y| x * y) |
| 165 | + // ^ would be okay, since it would be translated like this: |
| 166 | + // const STATIC_TRAIT_OBJECT: &'static mut FnMut(u32, u32) -> u32 |
| 167 | + // = &mut |x, y| x * y; |
| 168 | + // self.operator.unwrap_or(STATIC_TRAIT_OBJECT) |
| 169 | +} |
| 170 | + |
| 171 | +let d: &'static mut () = &mut (); |
| 172 | +let e: &'static mut Fn() -> u32 = &mut || 42; |
| 173 | +``` |
| 174 | + |
| 175 | +There are two ways this could be taken further with zero-sized types: |
| 176 | + |
| 177 | +1. Remove the `UnsafeCell` restriction if the type of the rvalue is zero-sized. |
| 178 | +2. The above, but also remove the __constexpr__ restriction, applying to any zero-sized rvalue instead. |
| 179 | + |
| 180 | +Both cases would work because one can't cause memory unsafety with a reference |
| 181 | +to a zero sized value, and they would allow more safe code to compile. |
| 182 | + |
| 183 | +However, they might complicated reasoning about the rules more, |
| 184 | +especially with the last one also being possibly confusing in regards to |
| 185 | +side-effects. |
| 186 | + |
| 187 | +Not doing this means: |
| 188 | + |
| 189 | +- Relying on `static` and `const` items to create `'static` references, which won't work in generics. |
| 190 | +- Empty-array expressions would remain special cased. |
| 191 | +- It would also not be possible to safely create `&'static mut` references to zero-sized |
| 192 | +types, though that part could also be achieved by allowing mutable references to |
| 193 | +zero-sized types in constants. |
| 194 | + |
| 195 | +# Unresolved questions |
| 196 | +[unresolved]: #unresolved-questions |
| 197 | + |
| 198 | +None, beyond "Should we do alternative 1 instead?". |
0 commit comments