Skip to content

Commit 3af4a21

Browse files
authored
Merge pull request #2342 from oli-obk/const-control-flow
Allow `if` and `match` in constants
2 parents b728bf6 + 9976afb commit 3af4a21

File tree

1 file changed

+143
-0
lines changed

1 file changed

+143
-0
lines changed

text/2342-const-control-flow.md

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
- Feature Name: `const-control-flow`
2+
- Start Date: 2018-01-11
3+
- RFC PR: [rust-lang/rfcs#2342](https://github.com/rust-lang/rfcs/pull/2342)
4+
- Rust Issue: [rust-lang/rust#49146](https://github.com/rust-lang/rust/issues/49146)
5+
6+
# Summary
7+
[summary]: #summary
8+
9+
Enable `if` and `match` during const evaluation and make them evaluate lazily.
10+
In short, this will allow `if x < y { y - x } else { x - y }` even though the
11+
else branch would emit an overflow error for unsigned types if `x < y`.
12+
13+
# Motivation
14+
[motivation]: #motivation
15+
16+
Conditions in constants are important for making functions like `NonZero::new`
17+
const fn and interpreting assertions.
18+
19+
# Guide-level explanation
20+
[guide-level-explanation]: #guide-level-explanation
21+
22+
If you write
23+
24+
```rust
25+
let x: u32 = ...;
26+
let y: u32 = ...;
27+
let a = x - y;
28+
let b = y - x;
29+
if x > y {
30+
// do something with a
31+
} else {
32+
// do something with b
33+
}
34+
```
35+
36+
The program will always panic (except if both `x` and `y` are `0`) because
37+
either `x - y` will overflow or `y - x` will. To resolve this one must move the
38+
`let a` and `let b` into the `if` and `else` branch respectively.
39+
40+
```rust
41+
let x: u32 = ...;
42+
let y: u32 = ...;
43+
if x > y {
44+
let a = x - y;
45+
// do something with a
46+
} else {
47+
let b = y - x;
48+
// do something with b
49+
}
50+
```
51+
52+
When constants are involved, new issues arise:
53+
54+
```rust
55+
const X: u32 = ...;
56+
const Y: u32 = ...;
57+
const FOO: SomeType = if X > Y {
58+
const A: u32 = X - Y;
59+
...
60+
} else {
61+
const B: u32 = Y - X;
62+
...
63+
};
64+
```
65+
66+
`A` and `B` are evaluated before `FOO`, since constants are by definition
67+
constant, so their order of evaluation should not matter. This assumption breaks
68+
in the presence of errors, because errors are side effects, and thus not pure.
69+
70+
To resolve this issue, one needs to eliminate the intermediate constants and
71+
directly evaluate `X - Y` and `Y - X`.
72+
73+
```rust
74+
const X: u32 = ...;
75+
const Y: u32 = ...;
76+
const FOO: SomeType = if X > Y {
77+
let a = X - Y;
78+
...
79+
} else {
80+
let b = Y - X;
81+
...
82+
};
83+
```
84+
85+
# Reference-level explanation
86+
[reference-level-explanation]: #reference-level-explanation
87+
88+
`match` on enums whose variants have no fields or `if` is translated during HIR
89+
-> MIR lowering to a `switchInt` terminator. Mir interpretation will now have to
90+
evaluate those terminators (which it already can).
91+
92+
`match` on enums with variants which have fields is translated to `switch`,
93+
which will check either the discriminant or compute the discriminant in the case
94+
of packed enums like `Option<&T>` (which has no special memory location for the
95+
discriminant, but encodes `None` as all zeros and treats everything else as a
96+
`Some`). When entering a `match` arm's branch, the matched on value is
97+
essentially transmuted to the enum variant's type, allowing further code to
98+
access its fields.
99+
100+
# Drawbacks
101+
[drawbacks]: #drawbacks
102+
103+
This makes it easier to fail compilation on random "constant" values like
104+
`size_of::<T>()` or other platform specific constants.
105+
106+
# Rationale and alternatives
107+
[alternatives]: #alternatives
108+
109+
## Require intermediate const fns to break the eager const evaluation
110+
111+
Instead of writing
112+
113+
```rust
114+
const X: u32 = ...;
115+
const Y: u32 = ...;
116+
const AB: u32 = if X > Y {
117+
X - Y
118+
} else {
119+
Y - X
120+
};
121+
```
122+
123+
where either `X - Y` or `Y - X` would emit an error, add an intermediate const fn
124+
125+
```rust
126+
const X: u32 = ...;
127+
const Y: u32 = ...;
128+
const fn foo(x: u32, y: u32) -> u32 {
129+
if x > y {
130+
x - y
131+
} else {
132+
y - x
133+
}
134+
}
135+
const AB: u32 = foo(x, y);
136+
```
137+
138+
Since the const fn's `x` and `y` arguments are unknown, they cannot be const
139+
evaluated. When the const fn is evaluated with given arguments, only the taken
140+
branch is evaluated.
141+
142+
# Unresolved questions
143+
[unresolved]: #unresolved-questions

0 commit comments

Comments
 (0)