Skip to content

Commit a18aafc

Browse files
committed
note on consts
1 parent 89b55a3 commit a18aafc

File tree

4 files changed

+104
-43
lines changed

4 files changed

+104
-43
lines changed

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@ which sort of is a virtual machine using `MIR` as "bytecode".
2121
## Table of Contents
2222

2323
* [Const Safety](const_safety.md)
24-
* [Constants](const.md)
25-
* [Promotion](promotion.md)
24+
* The three "kinds" of compile-time evaluated data:
25+
* [Statics](static.md) (`static`, `static mut`)
26+
* [Constants](const.md) (`const`, array sizes, non-`Copy` array initializers)
27+
* [Promoteds](promotion.md) (rvalue promotion)
2628

2729
## Related RFCs
2830

const.md

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1-
# Further restrictions for constants
1+
# Constants
22

3-
The [const safety](const_safety.md) concerns apply to all computations happening
4-
at compile-time, `static` and `const` alike. However, there are some additional
5-
considerations about `const` specifically. These arise from the idea that
3+
"Constants" in this document refers to `const` bodies, array sizes, and non-`Copy` array initializers.
4+
On top of what applies to [statics](static.md), they are subject to an additional constraint: In code like
65
```rust
76
const CONST: T = EXPR;
87
```
98
is supposed to behave as-if `EXPR` was written at every use site of `CONST`.
109

10+
Based on this requirement, we allow other constants and [promoteds](promotion.md) to read from constants.
11+
This is why the value of a `const` is subject to validity checks.
12+
1113
## References
1214

1315
One issue is constants of reference type:
@@ -23,31 +25,36 @@ const REF: &u32 = { const _VAL = EXPR; static _STATIC = EXPR; &_STATIC };
2325
(`EXPR` is assigned to a `const` first to make it subject to the restrictions
2426
discussed in this document.)
2527

26-
There are three reasons why this could be an issue.
28+
There are various reasons why this could be an issue.
2729

28-
### Pointer equality
30+
### 1. Pointer equality
2931

3032
We effectively "deduplicate" all the allocations that would otherwise locally be
3133
created at each use site of `REF`. This is observable when the program compares
3234
these pointers for equality. We consider this okay, i.e., programs may not rely
3335
on such constants all getting distinct addresses. They may not rely on them all
3436
getting the same address either.
3537

36-
### Interior mutability
38+
### 2. Interior mutability
3739

3840
If the reference has type `&Cell<i32>` it is quite clear that the program can
3941
easily observe whether two references point to the same memory even without
4042
comparing their address: Changes through one reference will affect reads through
4143
the other. So, we cannot allow constant references to types that have interior
42-
mutability (types that are not `Freeze`).
44+
mutability (types that are not `Freeze`):
45+
46+
```rust
47+
const BAD: &Cell<i32> = &Cell::new(42);
48+
// Inlining `BAD` everywhere clearly is not the same as them all pointing to the same thing.
49+
```
4350

4451
However, we can do better than that: Even if a *type* is not `Freeze`, it can
4552
have *values* that do not exhibit any interior mutability. For example, `&None`
4653
at type `&Option<Cell<i32>>` would be rejected by the naive analysis above, but
4754
is actually accepted by the compiler because we know that there is no
4855
`UnsafeCell` here that would permit interior mutability.
4956

50-
### `Sync`
57+
### 3. `Sync`
5158

5259
Finally, the same constant reference is actually shared across threads. This is
5360
very similar to multiple threads having a shared reference to the same `static`,
@@ -59,12 +66,9 @@ ecosystem that would break if we just started enforcing this now. See
5966
[this issue](https://github.com/rust-lang/rust/issues/49206) and the
6067
[PR attempting to fix this](https://github.com/rust-lang/rust/pull/54424/).
6168

62-
### `Drop`
69+
### 4. Drop
6370

64-
Values of "needs drop" types
65-
can only be used as the final initialization value of a `const` or `static` item.
66-
They may not be used as intermediate values that would be dropped before the item
67-
were initialized. As an example:
71+
`Drop` is actually not an issue, at least not more so than for statics:
6872

6973
```rust
7074
struct Foo;
@@ -76,19 +80,11 @@ impl Drop for Foo {
7680
}
7781

7882
const FOO: Foo = Foo; // Ok, drop is run at each use site in runtime code
79-
static FOOO: Foo = Foo; // Ok, drop is never run
8083

8184
// Not ok, cannot run `Foo::drop` because it's not a const fn
8285
const BAR: i32 = (Foo, 42).1;
8386
```
8487

85-
This restriction might be lifted in the future after trait impls
86-
may be declared `const` (https://github.com/rust-rfcs/const-eval/pull/8).
87-
88-
Note that in promoteds this restriction can never be lifted, because
89-
otherwise we would silently stop calling the `Drop` impl at runtime and
90-
pull it to much earlier (compile-time).
91-
9288
## Reading statics
9389

9490
Beyond values of reference type, we have to be careful that *computing* a

promotion.md

Lines changed: 50 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,30 @@
11
# Const promotion
22

3-
"Promotion" is a mechanism that affects code like `&3`: Instead of putting it on
4-
the stack, the `3` is allocated in global static memory and a reference with
5-
lifetime `'static` is provided. This is essentially an automatic transformation
6-
turning `&EXPR` into `{ const _PROMOTED = &EXPR; EXPR }`, but only if `EXPR`
7-
qualifies.
3+
["(Implicit) Promotion"][rfc] is a mechanism that affects code like `&3`:
4+
Instead of putting it on the stack, the `3` is allocated in global static memory
5+
and a reference with lifetime `'static` is provided. This is essentially an
6+
automatic transformation turning `&EXPR` into `{ const _PROMOTED = &EXPR; EXPR
7+
}`, but only if `EXPR` qualifies.
88

99
Note that promotion happens on the MIR, not on surface-level syntax. This is
1010
relevant when discussing e.g. handling of panics caused by overflowing
1111
arithmetic.
1212

13+
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+
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+
That's why we have to be very conservative with what can and cannot be promoted.
16+
17+
[rfc]: https://github.com/rust-lang/rfcs/blob/master/text/1414-rvalue_static_promotion.md
18+
1319
## Rules
1420

1521
### 1. Panics
1622

1723
Promotion is not allowed to throw away side effects. This includes panicking.
1824
Let us look at what happens when we promote `&(0_usize - 1)` in a debug build:
1925
We have to avoid erroring at compile-time, because that would be promotion
20-
breaking compilation (the code would have compiled just fine if we hadn't
21-
promoted), but we must be sure to error correctly at run-time. In the MIR, this
22-
looks roughly like
26+
breaking compilation, but we must be sure to error correctly at run-time. In
27+
the MIR, this looks roughly like
2328

2429
```
2530
_tmp1 = CheckedSub (const 0usize) (const 1usize)
@@ -89,18 +94,12 @@ but to abort compilation of a program that would have compiled fine if we would
8994
not have decided to promote. It is the responsibility of `foo` to not fail this
9095
way when working with const-safe arguments.
9196

92-
### 3. Constraints on constants
93-
94-
All the [extra restrictions for constants](const.md) beyond const safety also
95-
apply to promoteds, for the same reason: Evaluating the expression at
96-
compile-time instead of run-time should not alter program behavior.
97-
98-
### 4. Drop
97+
### 3. Drop
9998

100-
Expressions containing "needs drop" types
101-
can never be promoted. If such an expression were promoted, the `Drop` impl would
102-
never get called on the value, even though the user did not explicitly request such
103-
behavior by using an explicit `const` or `static` item.
99+
Expressions returning "needs drop" types can never be promoted. If such an
100+
expression were promoted, the `Drop` impl would never get called on the value,
101+
even though the user did not explicitly request such behavior by using an
102+
explicit `const` or `static` item.
104103

105104
As expression promotion is essentially the silent insertion of a `static` item, and
106105
`static` items never have their `Drop` impl called, the `Drop` impl of the promoted
@@ -111,6 +110,38 @@ it is unlikely to be the desired behavior in most cases and very likey to be con
111110
to the user. If such behavior is desired, the user can still use an explicit `static`
112111
or `const` item and refer to that.
113112

113+
## `&` in `const` and `static`
114+
115+
Promotion is also responsible for making code like this work:
116+
117+
```rust
118+
const FOO: &'static i32 = {
119+
let x = &13;
120+
x
121+
};
122+
```
123+
124+
However, since this is in explicit const context, we could be less strict about
125+
promotion in this situation.
126+
127+
Promotion is *not* involved in something like this:
128+
129+
```rust
130+
#![feature(const_vec_new)]
131+
const EMPTY_BYTES: &Vec<u8> = &Vec::new();
132+
133+
const NESTED: &'static Vec<u8> = {
134+
// This does not work when we have an inner scope:
135+
let x = &Vec::new(); //~ ERROR: temporary value dropped while borrowed
136+
x
137+
};
138+
```
139+
140+
In `EMPTY_BYTES`, the reference obtains the lifetime of the "enclosing scope",
141+
similar to how `let x = &mut x;` creates a reference whose lifetime lasts for
142+
the enclosing scope. This is decided during MIR building already, and does not
143+
involve promotion.
144+
114145
## Open questions
115146

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

static.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Statics
2+
3+
Statics (`static`, `static mut`) are the simplest kind of compile-time evaluated data:
4+
The user explicitly requested them to be evaluated at compile-time,
5+
so evaluation errors from computing the initial value of a static are no concern.
6+
They observably get evaluated *once*, with the result being put at some address known at run-time,
7+
so there are no fundamental restrictions on what statics can do.
8+
The compiler checks that statics are `Sync`, justifying sharing their address across threads.
9+
[Constants](const.md) and [promoteds](promotion.md) are not allowed to read from statics,
10+
so their final value does not have have to be [const-valid](const_safety.md) in any meaningful way
11+
(but as of 2019-08, we do check them for validity anyway, to be conservative).
12+
13+
## `Drop`
14+
15+
The compiler rejects intermediate values (created and discarded during the computation of a static initializer) that implement `Drop`.
16+
The reason for this is simply that the `Drop` implementation might be non-`const fn`.
17+
This restriction can be lifted once `const impl Drop for Type` (or something similar) is supported.
18+
19+
```rust
20+
struct Foo;
21+
22+
impl Drop for Foo {
23+
fn drop(&mut self) {
24+
println!("foo dropped");
25+
}
26+
}
27+
28+
static FOOO: Foo = Foo; // Ok, drop is never run
29+
30+
// Not ok, cannot run `Foo::drop` because it's not a const fn
31+
static BAR: i32 = (Foo, 42).1;
32+
```

0 commit comments

Comments
 (0)