Allow `if` and `match` in constants

This commit is contained in:
Oliver Schneider 2018-02-20 14:48:33 +01:00
parent 170631632a
commit 3a57a13d6a
No known key found for this signature in database
GPG Key ID: A69F8D225B3AD7D9
1 changed files with 136 additions and 0 deletions

View File

@ -0,0 +1,136 @@
- Feature Name: const-control-flow
- Start Date: 2018-01-11
- RFC PR: (leave this empty)
- Rust Issue: (leave this empty)
# Summary
[summary]: #summary
Enable `if` and `match` during const evaluation and make them evaluate lazily.
In short, this will allow `if x < y { y - x } else { x - y }` even though the
else branch would emit an overflow error for unsigned types if `x < y`.
# Motivation
[motivation]: #motivation
Conditions in constants are important for making functions like `NonZero::new`
const fn and interpreting assertions.
# Guide-level explanation
[guide-level-explanation]: #guide-level-explanation
If you write
```rust
let x: u32 = ...;
let y: u32 = ...;
let a = x - y;
let b = y - x;
if x > y {
// do something with a
} else {
// do something with b
}
```
The program will always panic (except if both `x` and `y` are `0`) because
either `x - y` will overflow or `y - x` will. To resolve this one must move the
`let a` and `let b` into the `if` and `else` branch respectively.
```rust
let x: u32 = ...;
let y: u32 = ...;
if x > y {
let a = x - y;
// do something with a
} else {
let b = y - x;
// do something with b
}
```
When constants are involved, new issues arise:
```rust
const X: u32 = ...;
const Y: u32 = ...;
const FOO: SomeType = if X > Y {
const A: u32 = X - Y;
...
} else {
const B: u32 = Y - X;
...
};
```
`A` and `B` are evaluated before `FOO`, since constants are by definition
constant, so their order of evaluation should not matter. This assumption breaks
in the presence of errors, because errors are side effects, and thus not pure.
To resolve this issue, one needs to eliminate the intermediate constants and
directly evaluate `X - Y` and `Y - X`.
```rust
const X: u32 = ...;
const Y: u32 = ...;
const FOO: SomeType = if X > Y {
let a = X - Y;
...
} else {
let b = Y - X;
...
};
```
# Reference-level explanation
[reference-level-explanation]: #reference-level-explanation
Currently interpreting `switch` and `switchInt` terminators is not allowed
during mir interpretation. This RFC proposes to allow them and ignore the
branches not taken, even if they contain errors. This removes another difference
between constant evaluation and runtime execution.
# Drawbacks
[drawbacks]: #drawbacks
This makes it easier to fail compilation on random "constant" values like
`size_of::<T>()` or other platform specific constants.
# Rationale and alternatives
[alternatives]: #alternatives
## Require intermediate const fns to break the eager const evaluation
Instead of writing
```rust
const X: u32 = ...;
const Y: u32 = ...;
const AB: u32 = if X > Y {
X - Y
} else {
Y - X
};
```
where either `X - Y` or `Y - X` would emit an error, add an intermediate const fn
```rust
const X: u32 = ...;
const Y: u32 = ...;
const fn foo(x: u32, y: u32) -> u32 {
if x > y {
x - y
} else {
y - x
}
}
const AB: u32 = foo(x, y);
```
Since the const fn's `x` and `y` arguments are unknown, they cannot be const
evaluated. When the const fn is evaluated with given arguments, only the taken
branch is evaluated.
# Unresolved questions
[unresolved]: #unresolved-questions