|
| 1 | +# Further restrictions for constants |
| 2 | + |
| 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 |
| 6 | +```rust |
| 7 | +const CONST: T = EXPR; |
| 8 | +``` |
| 9 | +is supposed to behave as-if `EXPR` was written at every use site of `CONST`. |
| 10 | + |
| 11 | +## References |
| 12 | + |
| 13 | +One issue is constants of reference type: |
| 14 | +```rust |
| 15 | +const REF: &u32 = &EXPR; |
| 16 | +``` |
| 17 | +Instead of creating a new allocation for storing the result of `EXPR` on every |
| 18 | +use site, we just have a single global "static" allocation and every use of |
| 19 | +`REF` uses its address. It's as if we had written: |
| 20 | +```rust |
| 21 | +const REF: &u32 = { const _VAL = EXPR; static _STATIC = EXPR; &_STATIC }; |
| 22 | +``` |
| 23 | +(`EXPR` is assigned to a `const` first to make it subject to the restrictions |
| 24 | +discussed in this document.) |
| 25 | + |
| 26 | +There are three reasons why this could be an issue. |
| 27 | + |
| 28 | +### Pointer equality |
| 29 | + |
| 30 | +We effectively "deduplicate" all the allocations that would otherwise locally be |
| 31 | +created at each use site of `REF`. This is observable when the program compares |
| 32 | +these pointers for equality. We consider this okay, i.e., programs may not rely |
| 33 | +on such constants all getting distinct addresses. They may not rely on them all |
| 34 | +getting the same address either. |
| 35 | + |
| 36 | +### Interior mutability |
| 37 | + |
| 38 | +If the reference has type `&Cell<i32>` it is quite clear that the program can |
| 39 | +easily observe whether two references point to the same memory even without |
| 40 | +comparing their address: Changes through one reference will affect reads through |
| 41 | +the other. So, we cannot allow constant references to types that have interior |
| 42 | +mutability (types that are not `Freeze`). |
| 43 | + |
| 44 | +However, we can do better than that: Even if a *type* is not `Freeze`, it can |
| 45 | +have *values* that do not exhibit any interior mutability. For example, `&None` |
| 46 | +at type `&Option<Cell<i32>>` would be rejected by the naive analysis above, but |
| 47 | +is actually accepted by the compiler because we know that there is no |
| 48 | +`UnsafeCell` here that would permit interior mutability. |
| 49 | + |
| 50 | +### `Sync` |
| 51 | + |
| 52 | +Finally, the same constant reference is actually shared across threads. This is |
| 53 | +very similar to multiple threads having a shared reference to the same `static`, |
| 54 | +which is why `static` must be `Sync`. So it seems like we should reject |
| 55 | +non-`Sync` types, conforming with the desugaring described above. |
| 56 | + |
| 57 | +However, this does not currently happen, and there are several crates across the |
| 58 | +ecosystem that would break if we just started enforcing this now. See |
| 59 | +[this issue](https://github.com/rust-lang/rust/issues/49206) and the |
| 60 | +[PR attempting to fix this](https://github.com/rust-lang/rust/pull/54424/). |
| 61 | + |
| 62 | +## Reading statics |
| 63 | + |
| 64 | +Beyond values of reference type, we have to be careful that *computing* a |
| 65 | +`const` cannot read from a static that could get mutated (because it is `static |
| 66 | +mut`, or because it has interior mutability). That would lead to the constant |
| 67 | +expression (being computed at compile-time) not having the same value any more |
| 68 | +when evaluated at run-time. |
| 69 | + |
| 70 | +This is distinct to the concern about interior mutability above: That concern |
| 71 | +was about first computing a `&Cell<i32>` and then using it at run-time (and |
| 72 | +observing the fact that it has been "deduplicated"), this here is about using |
| 73 | +such a value at compile-time even though it might be changed at run-time. |
0 commit comments