1
1
# Const promotion
2
2
3
- [ "(Implicit) Promotion"] [ promotion-rfc ] is a mechanism that affects code like ` &3 ` :
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.
6
+
7
+ ## Promotion contexts
8
+
9
+ There are a few different contexts where promotion is beneficial.
10
+
11
+ ### Lifetime extension
12
+
13
+ "Lifetime extension" is a mechanism that affects code like ` &3 ` :
4
14
Instead of putting it on the stack, the ` 3 ` is allocated in global static memory
5
15
and a reference with lifetime ` 'static ` is provided. This is essentially an
6
16
automatic transformation turning ` &EXPR ` into
@@ -10,17 +20,142 @@ Note that promotion happens on the MIR, not on surface-level syntax. This is
10
20
relevant when discussing e.g. handling of panics caused by overflowing
11
21
arithmetic.
12
22
23
+ Lifetime extension is described in [ RFC 1414] [ promotion-rfc ] . The RFC uses the
24
+ word "promotion" to refer exclusively to lifetime extension, since this was the
25
+ first context where promotion was done.
26
+
27
+ [ promotion-rfc ] : https://github.com/rust-lang/rfcs/blob/master/text/1414-rvalue_static_promotion.md
28
+
29
+ ### Non-` Copy ` array initialization
30
+
31
+ Another promotion context, the initializer of an array expression, was
32
+ introduced in [ RFC 2203] [ ] . Here, promotion allows arrays of
33
+ non-` Copy ` types to be initialized idiomatically, for example
34
+ ` [Option::<Box<i32>>::None; 32] ` .
35
+
36
+ [ RFC 2203 ] : https://github.com/rust-lang/rfcs/blob/master/text/2203-const-repeat-expr.md
37
+
38
+ ### ` #[rustc_args_required_const(...)] `
39
+
40
+ Additionally, some platform intrinsics require certain parameters to be
41
+ immediates (known at compile-time). We use the ` #[rustc_args_required_const] `
42
+ attribute, introduced in
43
+ [ rust-lang/rust #48018 ] ( https://github.com/rust-lang/rust/pull/48018 ) , to
44
+ specify these parameters and (aggressively, see below) try to promote the
45
+ corresponding arguments.
46
+
47
+ ### Implicit and explicit contexts
48
+
13
49
On top of what applies to [ consts] ( const.md ) , promoteds suffer from the additional issue that * the user did not ask for them to be evaluated at compile-time* .
14
50
Thus, if CTFE fails but the code would have worked fine at run-time, we broke the user's code for no good reason.
15
51
Even if we are sure we found an error in the user's code, we are only allowed to [ emit a warning, not a hard error] [ warn-rfc ] .
16
52
That's why we have to be very conservative with what can and cannot be promoted.
17
53
18
- [ promotion-rfc ] : https://github.com/rust-lang/rfcs/blob/master/text/1414-rvalue_static_promotion.md
54
+ For example, users might be surprised to learn that whenever they take a
55
+ reference to a temporary, that temporary may be promoted away and never
56
+ actually put on the stack. In this way, lifetime extension is an "implicit
57
+ promotion context": the user did not ask for the value to be promoted.
58
+
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
61
+ to be evaluated at compile-time even though they have not written it in a
62
+ ` const ` declaration. We call this an "explicit promotion context".
63
+
64
+ Currently, non-` Copy ` array initialization is treated as an implicit context.
65
+
66
+ The distinction between these two determines whether calls to arbitrary `const
67
+ fn` s (those without ` #[ rustc_promotable] `) are promotable (see below). See
68
+ [ rust-rfcs/const-eval #19 ] ( https://github.com/rust-rfcs/const-eval/issues/19 )
69
+ for a thorough discussion of this. At present, this is the only difference
70
+ between implicit and explicit contexts. The requirements for promotion in an
71
+ implicit context are a superset of the ones in an explicit context.
72
+
19
73
[ warn-rfc ] : https://github.com/rust-lang/rfcs/blob/master/text/1229-compile-time-asserts.md
20
74
21
- ## Rules
75
+ ### Promotion contexts inside ` const ` and ` static `
76
+
77
+ Lifetime extension is also responsible for making code like this work:
78
+
79
+ ``` rust
80
+ const FOO : & 'static i32 = {
81
+ let x = & 13 ;
82
+ x
83
+ };
84
+ ```
85
+
86
+ We defined above that promotion guarantees that code in a non-const context
87
+ will be executed at compile-time. The above example illustrates that lifetime
88
+ extension and non-` Copy ` array initialization are useful features * inside*
89
+ ` const ` s and ` static ` s as well. Strictly speaking, the transformation used to
90
+ enable these features inside a const-context is not promotion; no ` promoted ` s
91
+ are created in the MIR. However the same rules for promotability are used with
92
+ one modification: Because the user has already requested that this code run at
93
+ compile time, all contexts are treated as explicit.
94
+
95
+ Notice that some code involving ` & ` * looks* like it relies on lifetime
96
+ extension but actually does not:
97
+
98
+ ``` rust
99
+ const EMPTY_BYTES : & Vec <u8 > = & Vec :: new (); // Ok without lifetime extension
100
+ ```
101
+
102
+ As we have seen above, ` Vec::new() ` does not get promoted. And yet this
103
+ compiles. Why that? The reason is that the reference obtains the lifetime of
104
+ the "enclosing scope", similar to how ` let x = &mut x; ` creates a reference
105
+ whose lifetime lasts for the enclosing scope. This is decided during MIR
106
+ building already, and does not involve lifetime extension.
107
+
108
+ ## Promotability
109
+
110
+ We have described the circumstances where promotion is desirable, but what
111
+ expressions are actually eligible for promotion? We refer to eligible
112
+ expressions as "promotable" and describe the restrictions on such expressions
113
+ below.
114
+
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 ` .
22
157
23
- ### 1. Panics
158
+ ### Panics
24
159
25
160
Promotion is not allowed to throw away side effects. This includes panicking.
26
161
Let us look at what happens when we promote ` &(0_usize - 1) ` in a debug build:
@@ -56,7 +191,7 @@ could not panic!) at run-time leads to a compile-time CTFE error.
56
191
* Dynamic check.* The Miri engine already dynamically detects panics, but the
57
192
main point of promoteds is ruling them out statically.
58
193
59
- ### 2. Const safety
194
+ ### Const safety
60
195
61
196
We have explained what happens when evaluating a promoted panics, but what about
62
197
other kinds of failure -- what about hitting an unsupported operation or
@@ -105,7 +240,7 @@ For this reason, only `const fn` that were explicitly marked with the
105
240
* Dynamic check.* The Miri engine already dynamically detects const safety
106
241
violations, but the main point of promoteds is ruling them out statically.
107
242
108
- ### 3. Drop
243
+ ### Drop
109
244
110
245
Expressions returning "needs drop" types can never be promoted. If such an
111
246
expression were promoted, the ` Drop ` impl would never get called on the value,
@@ -124,52 +259,6 @@ or `const` item and refer to that.
124
259
* Dynamic check.* The Miri engine could dynamically check this by ensuring that
125
260
the result of computing a promoted is a value that does not need dropping.
126
261
127
- ## ` & ` in ` const ` and ` static `
128
-
129
- Promotion is also responsible for making code like this work:
130
-
131
- ``` rust
132
- const FOO : & 'static i32 = {
133
- let x = & 13 ;
134
- x
135
- };
136
- ```
137
-
138
- However, since this is in explicit const context, we are less strict about
139
- promotion in this situation: all function calls are promoted, not just
140
- ` #[rustc_promotable] ` functions:
141
-
142
- ``` rust
143
- const fn bar () -> i32 { 42 }
144
-
145
- const FOO : & 'static i32 = {
146
- let x = & bar (); // this gets promoted
147
- x
148
- };
149
- ```
150
-
151
- However, we still do not promote * everything* ; e.g., drop-checking still applies:
152
-
153
- ``` rust
154
- const DROP : & 'static Vec <u8 > = {
155
- let x = & Vec :: new (); // ~ ERROR: temporary value dropped while borrowed
156
- x
157
- };
158
- ```
159
-
160
- Notice that some code involving ` & ` * looks* like it relies on promotion but
161
- actually does not:
162
-
163
- ``` rust
164
- const EMPTY_BYTES : & Vec <u8 > = & Vec :: new (); // Ok without promotion
165
- ```
166
-
167
- As we have seen above, ` Vec::new() ` does not get promoted. And yet this
168
- compiles. Why that? The reason is that the reference obtains the lifetime of
169
- the "enclosing scope", similar to how ` let x = &mut x; ` creates a reference
170
- whose lifetime lasts for the enclosing scope. This is decided during MIR
171
- building already, and does not involve promotion.
172
-
173
262
## Open questions
174
263
175
264
* There is a fourth kind of CTFE failure -- resource exhaustion. What do we do
0 commit comments