Skip to content

Commit c794c75

Browse files
authored
Merge pull request #2344 from oli-obk/const_looping
Allow `loop` in constant evaluation
2 parents 5a44ed0 + e4ac9b0 commit c794c75

File tree

1 file changed

+107
-0
lines changed

1 file changed

+107
-0
lines changed

text/2344-const-looping.md

+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
- Feature Name: `const_looping`
2+
- Start Date: 2018-02-18
3+
- RFC PR: [rust-lang/rfcs#2344](https://github.com/rust-lang/rfcs/pull/2344)
4+
- Rust Issue: [rust-lang/rust#52000](https://github.com/rust-lang/rust/issues/52000)
5+
6+
# Summary
7+
[summary]: #summary
8+
9+
Allow the use of `loop`, `while` and `while let` during constant evaluation.
10+
`for` loops are technically allowed, too, but can't be used in practice because
11+
each iteration calls `iterator.next()`, which is not a `const fn` and thus can't
12+
be called within constants. Future RFCs (like
13+
https://github.com/rust-lang/rfcs/pull/2237) might lift that restriction.
14+
15+
# Motivation
16+
[motivation]: #motivation
17+
18+
Any iteration is expressible with recursion. Since we already allow recursion
19+
via const fn and termination of said recursion via `if` or `match`, all code
20+
enabled by const recursion is already legal now. Some algorithms are better
21+
expressed as imperative loops and a lot of Rust code uses loops instead of
22+
recursion. Allowing loops in constants will allow more functions to become const
23+
fn without requiring any changes.
24+
25+
# Guide-level explanation
26+
[guide-level-explanation]: #guide-level-explanation
27+
28+
If you previously had to write functional code inside constants, you can now
29+
change it to imperative code. For example if you wrote a fibonacci like
30+
31+
```rust
32+
const fn fib(n: u128) -> u128 {
33+
match n {
34+
0 => 1,
35+
1 => 1,
36+
n => fib(n - 1) + fib(n + 1)
37+
}
38+
}
39+
```
40+
41+
which takes exponential time to compute a fibonacci number, you could have
42+
changed it to the functional loop
43+
44+
```rust
45+
const fn fib(n: u128) -> u128 {
46+
const fn helper(n: u128, a: u128, b: u128, i: u128) -> u128 {
47+
if i <= n {
48+
helper(n, b, a + b, i + 1)
49+
} else {
50+
b
51+
}
52+
}
53+
helper(n, 1, 1, 2)
54+
}
55+
```
56+
57+
but now you can just write it as an imperative loop, which also finishes in
58+
linear time.
59+
60+
```rust
61+
const fn fib(n: u128) -> u128 {
62+
let mut a = 1;
63+
let mut b = 1;
64+
let mut i = 2;
65+
while i <= n {
66+
let tmp = a + b;
67+
a = b;
68+
b = tmp;
69+
i += 1;
70+
}
71+
b
72+
}
73+
```
74+
75+
# Reference-level explanation
76+
[reference-level-explanation]: #reference-level-explanation
77+
78+
A loop in MIR is a cyclic graph of `BasicBlock`s. Evaluating such a loop is no
79+
different from evaluating a linear sequence of `BasicBlock`s, except that
80+
termination is not guaranteed. To ensure that the compiler never hangs
81+
indefinitely, we count the number of terminators processed and whenever we reach
82+
a fixed limit, we report a lint mentioning that we cannot guarantee that the
83+
evaluation will terminate and reset the counter to zero. This lint should recur
84+
in a non-annoying amount of time (e.g. at least 30 seconds between occurrences).
85+
This means that there's an internal deterministic counter (for the terminators) and
86+
a timestamp of the last (if any) loop warning emission. Both the counter needs to reach
87+
its limit and 30 seconds have to have passed since the last warning emission in order
88+
for a new warning to be emitted.
89+
90+
# Drawbacks
91+
[drawbacks]: #drawbacks
92+
93+
* Infinite loops will cause the compiler to never finish if the lint is not denied
94+
95+
# Rationale and alternatives
96+
[alternatives]: #alternatives
97+
98+
- Do nothing, users can keep using recursion
99+
100+
# Unresolved questions
101+
[unresolved]: #unresolved-questions
102+
103+
* Should we add a true recursion check that hashes the interpreter state and
104+
detects if it has reached the same state again?
105+
* This will slow down const evaluation enormously and for complex iterations
106+
is essentially useless because it'll take forever (e.g. counting from 0 to
107+
`u64::max_value()`)

0 commit comments

Comments
 (0)