Skip to content

Commit 01fa6c2

Browse files
committed
update promotion docs
1 parent 07328a7 commit 01fa6c2

File tree

1 file changed

+65
-70
lines changed

1 file changed

+65
-70
lines changed

promotion.md

+65-70
Original file line numberDiff line numberDiff line change
@@ -2,80 +2,74 @@
22

33
"Promotion" is the act of splicing a part of a MIR computation out into a
44
separate self-contained MIR body which is evaluated at compile-time like a
5-
constant.
5+
constant. This mechanism has been introduced by [RFC 1414][promotion-rfc] with
6+
the goal of equipping some references-to-temporaries with a `'static` lifetime,
7+
which is sometimes called "lifetime extension".
68

7-
## Promotion contexts
8-
9-
There are a few different contexts where promotion is beneficial.
10-
11-
### Lifetime extension
9+
[promotion-rfc]: https://github.com/rust-lang/rfcs/blob/master/text/1414-rvalue_static_promotion.md
1210

13-
"Lifetime extension" is a mechanism that affects code like `&3`:
14-
Instead of putting it on the stack, the `3` is allocated in global static memory
15-
and a reference with lifetime `'static` is provided. This is essentially an
16-
automatic transformation turning `&EXPR` into `{ const _PROMOTED = &EXPR;
17-
_PROMOTED }`, but only if `EXPR` qualifies. Topmost projections are not
18-
promoted, so `&EXPR.proj1.proj2` turns into `{ const _PROMOTED = &EXPR;
19-
&(*_PROMOTED).proj1.proj2 }`.
11+
Promotion / lifetime extension affects code like `&3`: Instead of putting it on
12+
the stack, the `3` is allocated in global static memory and a reference with
13+
lifetime `'static` is provided. This is essentially an automatic transformation
14+
turning `&EXPR` into `{ const _PROMOTED = &EXPR; _PROMOTED }`, but only if
15+
`EXPR` qualifies. Topmost projections are not promoted, so `&EXPR.proj1.proj2`
16+
turns into `{ const _PROMOTED = &EXPR; &(*_PROMOTED).proj1.proj2 }`.
2017

2118
Note that promotion happens on the MIR, not on surface-level syntax. This is
2219
relevant when discussing e.g. handling of panics caused by overflowing
2320
arithmetic.
2421

25-
Lifetime extension is described in [RFC 1414][promotion-rfc]. The RFC uses the
26-
word "promotion" to refer exclusively to lifetime extension, since this was the
27-
first context where promotion was done.
28-
29-
[promotion-rfc]: https://github.com/rust-lang/rfcs/blob/master/text/1414-rvalue_static_promotion.md
30-
31-
### Non-`Copy` array initialization
32-
33-
Another promotion context, the initializer of an array expression, was
34-
introduced in [RFC 2203][]. Here, promotion allows arrays of
35-
non-`Copy` types to be initialized idiomatically, for example
36-
`[Option::<Box<i32>>::None; 32]`.
37-
38-
[RFC 2203]: https://github.com/rust-lang/rfcs/blob/master/text/2203-const-repeat-expr.md
39-
40-
### `#[rustc_args_required_const(...)]` and inline assembly `const` operands
41-
42-
Additionally, some platform intrinsics require certain parameters to be
43-
immediates (known at compile-time). We use the `#[rustc_args_required_const]`
44-
attribute, introduced in
45-
[rust-lang/rust#48018](https://github.com/rust-lang/rust/pull/48018), to
46-
specify these parameters and (aggressively, see below) try to promote the
47-
corresponding arguments.
22+
## Promotion and fallability of const-evaluation
4823

49-
Similarly, inline assembly has `const` operands, which are treated the same way
50-
as `rustc_args_required_const` arguments.
24+
On top of what applies to [consts](const.md), promoteds suffer from the
25+
additional issue that *the user did not ask for them to be evaluated at
26+
compile-time*. Thus, if CTFE fails but the code would have worked fine at
27+
run-time, we broke the user's code for no good reason. Even if we are sure we
28+
found an error in the user's code, we are only allowed to
29+
[emit a warning, not a hard error][warn-rfc].
5130

52-
## Implicit and explicit promotion
53-
54-
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*.
55-
Thus, if CTFE fails but the code would have worked fine at run-time, we broke the user's code for no good reason.
56-
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].
57-
We call this *implicit* promotion, and we have to be very conservative with what can and cannot be implicitly promoted.
58-
59-
CTFE of implicitly promoted code must never fail to evaluate except if the
60-
run-time code also would have failed. This means we cannot permit calling
61-
arbitrary `const fn`, as discussed in detail in
31+
For example:
32+
```rust
33+
fn foo() {
34+
if false {
35+
let x = &(1/0);
36+
}
37+
}
38+
```
39+
If we performed promotion here, this would turn into
40+
```rust
41+
fn foo() {
42+
if false {
43+
const _PROMOTED = &(1/0);
44+
let x = _PROMOTED;
45+
}
46+
}
47+
```
48+
When compiling this function, we have to evaluate all constants that occur
49+
inside the function body, even if they might only be used in dead code. This
50+
means we have to evaluate `_PROMOTED`, which will error -- and now what, should
51+
we halt compilation? That would be wrong since there is no problem with this
52+
code, the "bad" division never actually happens as it occurs in dead code.
53+
(Note that the considerations would be the same even if `foo` were a `const
54+
fn`.)
55+
56+
As a consequence, we only promote code that can never fail to evaluate (see
57+
[RFC 3027]). This ensures that even if promotion happens inside dead code, this
58+
will not turn a "runtime error in dead code" (which is not an error at all) into
59+
a compile-time error. In particular, we cannot promote calls to arbitrary `const
60+
fn`, as discussed in detail in
6261
[rust-lang/const-eval#19](https://github.com/rust-lang/const-eval/issues/19).
63-
Thus, only functions marked `#[rustc_promotable]` are implicitly promotable (see
64-
below).
65-
66-
On the other hand, when a user passes an expression to a function with
67-
`#[rustc_args_required_const]`, the only way for this code to compile is to
68-
promote it. In that sense, the user is explicitly asking for that expression to
69-
be evaluated at compile-time even though they have not written it in a `const`
70-
declaration. We can thus be less conservative. This is called *explicit*
71-
promotion.
62+
Thus, only functions marked `#[rustc_promotable]` are promotable.
7263

73-
Currently, the following are considered explicit promotion contexts:
74-
* `#[rustc_args_required_const]` arguments and inline assembly `const` operands everywhere.
75-
* Everything inside the bodies of `const` and `static` items. (Note: this is handled separately from "explicit contexts" in promotion analysis, but the effect is the same.
76-
The arguments given above for justifying explicit promotion do not apply here. Currently, this works out because failing to evaluate one of these promoteds just leads to a warning, but longer-term it would be desirable to turn evaluation failures into hard errors, which for these promoteds means we have to guarantee that we only evaluate them on-demand.)
64+
[RFC 3027]: https://rust-lang.github.io/rfcs/3027-infallible-promotion.html
7765

78-
In these contexts, we promote calls to arbitrary `const fn`.
66+
There is one exception to this rule: the bodies of `const`/`static`
67+
initializers. This code is never compiled, so we do not actually have to
68+
evaluate constants that occur in dead code. If we are careful enough during
69+
compilation, we can ensure that only constants whose value is *actually needed*
70+
are evaluated. We thus can be more relaxed about promotion; in practice, what
71+
this means is that we will promote calls to arbitrary `const fn`, not just those
72+
marked `#[rustc_promotable]`.
7973

8074
[See below][static access] for another special case in promotion analysis:
8175
accesses and references to statics are only promoted inside other statics.
@@ -84,8 +78,8 @@ accesses and references to statics are only promoted inside other statics.
8478

8579
## "enclosing scope" rule
8680

87-
Notice that some code involving `&` *looks* like it relies on lifetime
88-
extension but actually does not:
81+
Notice that some code involving `&` *looks* like it relies on promotion /
82+
lifetime extension but actually does not:
8983

9084
```rust
9185
const EMPTY_BYTES: &Vec<u8> = &Vec::new(); // Ok without lifetime extension
@@ -117,12 +111,13 @@ restrictions described there are needed because we want `const` to behave the
117111
same as copying the `const` initializer everywhere the constant is used; we need
118112
the same property when promoting expressions. But we need more.
119113

120-
Note that there is no point in doing additional dynamic checks here. The entire point of
121-
the promotion restrictions is to avoid failing compilation for code that would
122-
have been fine without promotion. The best a dynamic check could do is tell us
123-
after the fact that we should not have promoted something, but then it is
124-
already too late -- and the dynamic checks for that are exactly the ones we are
125-
already doing for constants and statics.
114+
Note that there is no point in doing additional dynamic checks to ensure that we
115+
do get these restrictions right. The entire point of the promotion restrictions
116+
is to avoid failing compilation for code that would have been fine without
117+
promotion. The best a dynamic check could do is tell us after the fact that we
118+
should not have promoted something, but then it is already too late -- and the
119+
dynamic checks for that are exactly the ones we are already doing for constants
120+
and statics.
126121

127122
### Panics
128123

0 commit comments

Comments
 (0)