|
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