|
1 | 1 | # Const promotion |
2 | 2 |
|
3 | | -"Promotion" is the act of guaranteeing that code not written in a const context |
4 | | -(e.g. initalizer of a `const` or `static`, or an array length expression) will |
5 | | -be run at compile-time. |
| 3 | +"Promotion" is the act of guaranteeing that code *not* written in an (explicit) |
| 4 | +const context will be run at compile-time. Explicit const contexts include the |
| 5 | +initializer of a `const` or `static`, or an array length expression. |
6 | 6 |
|
7 | 7 | ## Promotion contexts |
8 | 8 |
|
@@ -57,11 +57,14 @@ actually put on the stack. In this way, lifetime extension is an "implicit |
57 | 57 | promotion context": the user did not ask for the value to be promoted. |
58 | 58 |
|
59 | 59 | On the other hand, when a user passes an expression to a function with |
60 | | -`#[rustc_args_required_const]`, they are explicitly asking for that expression |
| 60 | +`#[rustc_args_required_const]`, the only way for this code to compile is to promote it. |
| 61 | +In that sense, the user is explicitly asking for that expression |
61 | 62 | to be evaluated at compile-time even though they have not written it in a |
62 | 63 | `const` declaration. We call this an "explicit promotion context". |
63 | 64 |
|
64 | | -Currently, non-`Copy` array initialization is treated as an implicit context. |
| 65 | +Currently, non-`Copy` array initialization is treated as an implicit context, |
| 66 | +because the code could compile even without promotion (namely, if the result |
| 67 | +type is `Copy`). |
65 | 68 |
|
66 | 69 | The distinction between these two determines whether calls to arbitrary `const |
67 | 70 | fn`s (those without `#[rustc_promotable]`) are promotable (see below). See |
@@ -112,48 +115,10 @@ expressions are actually eligible for promotion? We refer to eligible |
112 | 115 | expressions as "promotable" and describe the restrictions on such expressions |
113 | 116 | below. |
114 | 117 |
|
115 | | -### Named locals |
116 | | - |
117 | | -Promotable expressions cannot refer to named locals. This is not a technical |
118 | | -limitation with the CTFE engine. While writing `let x = {expr}` outside of a |
119 | | -const context, the user likely expects that `x` will live on the stack and be |
120 | | -initialized at run-time. Although this is not (to my knowledge) guaranteed by |
121 | | -the language, we do not wish to violate the user's expectations here. |
122 | | - |
123 | | -### Single assignment |
124 | | - |
125 | | -We only promote temporaries that are assigned to exactly once. For example, the |
126 | | -lifetime of the temporary whose reference is assigned to `x` below will not be |
127 | | -extended. |
128 | | - |
129 | | -```rust |
130 | | -let x: &'static i32 = &if cfg!(windows) { 0 } else { 1 }; |
131 | | -``` |
132 | | - |
133 | | -Once again, this is not a fundamental limitation in the CTFE engine; we are |
134 | | -perfectly capable of evaluating such expressions at compile time. However, |
135 | | -determining the promotability of complex expressions would require more |
136 | | -resources for little benefit. |
137 | | - |
138 | | -### Access to a `const` or `static` |
139 | | - |
140 | | -When accessing a `const` in a promotable context, the restrictions on single |
141 | | -assignment and named locals do not apply to the body of the `const`. All other |
142 | | -restrictions, notably that the result of the `const` cannot be `Drop` or mutable |
143 | | -through a reference still apply. For instance, while the previous example was |
144 | | -not legal, the following would be: |
145 | | - |
146 | | -```rust |
147 | | -const BOOL: i32 = { |
148 | | - let ret = if cfg!(windows) { 0 } else { 1 }; |
149 | | - ret |
150 | | -}; |
151 | | - |
152 | | -let x: &'static i32 = &BOOL; |
153 | | -``` |
154 | | - |
155 | | -An access to a `static` is only promotable within the initializer of |
156 | | -another `static`. |
| 118 | +First of all, expressions have to be [allowed in constants](const.md). The |
| 119 | +restrictions described there are needed because we want `const` to behave the |
| 120 | +same as copying the `const` initializer everywhere the constant is used; we need |
| 121 | +the same property when promoting expressions. But we need more. |
157 | 122 |
|
158 | 123 | ### Panics |
159 | 124 |
|
@@ -259,6 +224,77 @@ or `const` item and refer to that. |
259 | 224 | *Dynamic check.* The Miri engine could dynamically check this by ensuring that |
260 | 225 | the result of computing a promoted is a value that does not need dropping. |
261 | 226 |
|
| 227 | +### Access to a `const` or `static` |
| 228 | + |
| 229 | +When accessing a `const` in a promotable context, its value gets computed |
| 230 | +at compile-time anyway, so we do not have to check the initializer. However, the |
| 231 | +restrictions described above still apply for the *result* of the promoted |
| 232 | +computation: in particular, it must be a valid `const` (i.e., it cannot |
| 233 | +introduce interior mutability) and it must not require dropping. |
| 234 | + |
| 235 | +For instance the following would be legal even though calls to `do_it` are not |
| 236 | +eligible for implicit promotion: |
| 237 | + |
| 238 | +```rust |
| 239 | +const fn do_it(x: i32) -> i32 { 2*x } |
| 240 | +const ANSWER: i32 = { |
| 241 | + let ret = do_it(21); |
| 242 | + ret |
| 243 | +}; |
| 244 | + |
| 245 | +let x: &'static i32 = &ANSWER; |
| 246 | +``` |
| 247 | + |
| 248 | +An access to a `static` is only promotable within the initializer of another |
| 249 | +`static`. This is for the same reason that `const` initializers |
| 250 | +[cannot access statics](const.md#reading-statics). |
| 251 | + |
| 252 | +Crucially, however, the following is *not* legal: |
| 253 | + |
| 254 | +```rust |
| 255 | +const X: Cell<i32> = Cell::new(5); // ok |
| 256 | +const XREF: &Cell<i32> = &X; // not ok |
| 257 | +fn main() { |
| 258 | + let x: &'static _ = &X; // not ok |
| 259 | +} |
| 260 | +``` |
| 261 | + |
| 262 | +Just like allowing `XREF` would be a problem because, by the inlining semantics, |
| 263 | +every user of `XREF` should get their own `Cell`; it would also be a problem to |
| 264 | +promote here because if that code gets executed multiple times (e.g. inside a |
| 265 | +loop), it should get a new `Cell` each time. |
| 266 | + |
| 267 | +### Named locals |
| 268 | + |
| 269 | +Promotable expressions cannot refer to named locals. This is not a technical |
| 270 | +limitation with the CTFE engine. While writing `let x = {expr}` outside of a |
| 271 | +const context, the user likely expects that `x` will live on the stack and be |
| 272 | +initialized at run-time. Although this is not (to my knowledge) guaranteed by |
| 273 | +the language, we do not wish to violate the user's expectations here. |
| 274 | + |
| 275 | +Note that constant-folding still applies: the optimizer may compute `x` at |
| 276 | +compile-time and even inline it everywhere if it can show that this does not |
| 277 | +observably alter program behavior. Promotion is very different from |
| 278 | +constant-folding as promotion can introduce observable differences in behavior |
| 279 | +(if const-evaluation fails) and as it is *guaranteed* to happen in some cases |
| 280 | +(and thus exploited by the borrow checker). This is reflected in the fact that |
| 281 | +promotion affects lifetimes, but constant folding does not. |
| 282 | + |
| 283 | +### Single assignment |
| 284 | + |
| 285 | +We only promote temporaries that are assigned to exactly once. For example, the |
| 286 | +lifetime of the temporary whose reference is assigned to `x` below will not be |
| 287 | +extended. |
| 288 | + |
| 289 | +```rust |
| 290 | +let x: &'static i32 = &if cfg!(windows) { 0 } else { 1 }; |
| 291 | +``` |
| 292 | + |
| 293 | +Once again, this is not a fundamental limitation in the CTFE engine; we are |
| 294 | +perfectly capable of evaluating such expressions at compile time. However, |
| 295 | +determining the promotability of complex expressions would require more |
| 296 | +resources for little benefit. |
| 297 | + |
262 | 298 | ## Open questions |
263 | 299 |
|
264 | 300 | * There is a fourth kind of CTFE failure -- resource exhaustion. What do we do |
|
0 commit comments