Skip to content

Commit 220938f

Browse files
authored
Define promotion contexts and promotability (#28)
Define promotion contexts and promotability
2 parents 4c15a11 + 3ccf570 commit 220938f

File tree

1 file changed

+141
-52
lines changed

1 file changed

+141
-52
lines changed

promotion.md

+141-52
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
# Const promotion
22

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`:
414
Instead of putting it on the stack, the `3` is allocated in global static memory
515
and a reference with lifetime `'static` is provided. This is essentially an
616
automatic transformation turning `&EXPR` into
@@ -10,17 +20,142 @@ Note that promotion happens on the MIR, not on surface-level syntax. This is
1020
relevant when discussing e.g. handling of panics caused by overflowing
1121
arithmetic.
1222

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+
1349
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*.
1450
Thus, if CTFE fails but the code would have worked fine at run-time, we broke the user's code for no good reason.
1551
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].
1652
That's why we have to be very conservative with what can and cannot be promoted.
1753

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+
1973
[warn-rfc]: https://github.com/rust-lang/rfcs/blob/master/text/1229-compile-time-asserts.md
2074

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`.
22157

23-
### 1. Panics
158+
### Panics
24159

25160
Promotion is not allowed to throw away side effects. This includes panicking.
26161
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.
56191
*Dynamic check.* The Miri engine already dynamically detects panics, but the
57192
main point of promoteds is ruling them out statically.
58193

59-
### 2. Const safety
194+
### Const safety
60195

61196
We have explained what happens when evaluating a promoted panics, but what about
62197
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
105240
*Dynamic check.* The Miri engine already dynamically detects const safety
106241
violations, but the main point of promoteds is ruling them out statically.
107242

108-
### 3. Drop
243+
### Drop
109244

110245
Expressions returning "needs drop" types can never be promoted. If such an
111246
expression were promoted, the `Drop` impl would never get called on the value,
@@ -124,52 +259,6 @@ or `const` item and refer to that.
124259
*Dynamic check.* The Miri engine could dynamically check this by ensuring that
125260
the result of computing a promoted is a value that does not need dropping.
126261

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-
173262
## Open questions
174263

175264
* There is a fourth kind of CTFE failure -- resource exhaustion. What do we do

0 commit comments

Comments
 (0)