From bcffd357a5cb0c2f25d3a319559220e16f0dd509 Mon Sep 17 00:00:00 2001 From: Oliver Schneider Date: Thu, 22 Feb 2018 14:43:59 +0100 Subject: [PATCH 1/5] Allow `loop` in constant evaluation --- text/0000-const-looping.md | 98 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 text/0000-const-looping.md diff --git a/text/0000-const-looping.md b/text/0000-const-looping.md new file mode 100644 index 00000000000..ab43a1f6469 --- /dev/null +++ b/text/0000-const-looping.md @@ -0,0 +1,98 @@ +- Feature Name: const_looping +- Start Date: 2018-02-18 +- RFC PR: (leave this empty) +- Rust Issue: (leave this empty) + +# Summary +[summary]: #summary + +Allow the use of `loop`, `while` and `while let` during constant evaluation. +`for` loops are technically allowed, too, but can't be used in practice because +each iteration calls `iterator.next()`, which is not a `const fn` and thus can't +be called within constants. + +# Motivation +[motivation]: #motivation + +Any iteration is expressible as a recursion. Since we already allow recursion +via const fn and termination of said recursion via `if` or `match`, all code +enabled by const recursion is already legal now. Writing loops with recursion is +very tedious and can quickly become unreadable, while regular loops are much +more natural in Rust. + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +If you previously had to write functional code inside constants, you can now +change it to imperative code. For example if you wrote a fibonacci like + +```rust +const fn fib(n: u128) -> u128 { + match n { + 0 => 1, + 1 => 1, + n => fib(n - 1) + fib(n + 1) + } +} +``` + +which takes exponential time to compute a fibonacci number, you could have +changed it to the functional loop + +```rust +const fn fib(n: u128) -> u128 { + const fn helper(n: u128, a: u128, b: u128, i: u128) -> u128 { + if i <= n { + helper(n, b, a + b, i + 1) + } else { + b + } + } + helper(n, 1, 1, 2) +} +``` + +but now you can just write it as an imperative loop, which also finishes in +linear time. + +```rust +const fn fib(n: u128) -> u128 { + let mut a = 1; + let mut b = 1; + let mut i = 2; + while i <= n { + let tmp = a + b; + a = b; + b = tmp; + i += 1; + } + b +} +``` + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +A loop in MIR is a cyclic graph of BasicBlocks. Evaluating such a loop is no +different from evaluating a linear sequence of BasicBlocks, except that +termination is not guaranteed. To ensure that the compiler never hangs +indefinitely, we count the number of terminators processed and once we reach a +fixed limit, we report an error mentioning that we aborted constant evaluation, +because we could not guarantee that it'll terminate. + +# Drawbacks +[drawbacks]: #drawbacks + +* Loops are not guaranteed to terminate + * We catch this already by having a maximum number of basic blocks that we + can evaluate. +* A guaranteed to terminate, non looping constant might trigger the limit, if it + has too much code. + +# Rationale and alternatives +[alternatives]: #alternatives + +- Do nothing, users can keep using recursion + +# Unresolved questions +[unresolved]: #unresolved-questions From 5d3d23d8ac16e12871b1f272dc46946228507fce Mon Sep 17 00:00:00 2001 From: Oliver Schneider Date: Thu, 22 Feb 2018 15:58:40 +0100 Subject: [PATCH 2/5] Address comments --- text/0000-const-looping.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/text/0000-const-looping.md b/text/0000-const-looping.md index ab43a1f6469..136a5697b95 100644 --- a/text/0000-const-looping.md +++ b/text/0000-const-looping.md @@ -9,16 +9,18 @@ Allow the use of `loop`, `while` and `while let` during constant evaluation. `for` loops are technically allowed, too, but can't be used in practice because each iteration calls `iterator.next()`, which is not a `const fn` and thus can't -be called within constants. +be called within constants. Future RFCs (like +https://github.com/rust-lang/rfcs/pull/2237) might lift that restriction. # Motivation [motivation]: #motivation -Any iteration is expressible as a recursion. Since we already allow recursion +Any iteration is expressible with recursion. Since we already allow recursion via const fn and termination of said recursion via `if` or `match`, all code -enabled by const recursion is already legal now. Writing loops with recursion is -very tedious and can quickly become unreadable, while regular loops are much -more natural in Rust. +enabled by const recursion is already legal now. Some algorithms are better +expressed as imperative loops and a lot of Rust code uses loops instead of +recursion. Allowing loops in constants will allow more functions to become const +fn without requiring any changes. # Guide-level explanation [guide-level-explanation]: #guide-level-explanation @@ -73,8 +75,8 @@ const fn fib(n: u128) -> u128 { # Reference-level explanation [reference-level-explanation]: #reference-level-explanation -A loop in MIR is a cyclic graph of BasicBlocks. Evaluating such a loop is no -different from evaluating a linear sequence of BasicBlocks, except that +A loop in MIR is a cyclic graph of `BasicBlock`s. Evaluating such a loop is no +different from evaluating a linear sequence of `BasicBlock`s, except that termination is not guaranteed. To ensure that the compiler never hangs indefinitely, we count the number of terminators processed and once we reach a fixed limit, we report an error mentioning that we aborted constant evaluation, From 4c884b4552f702763d14f4a4f2e9c4b5b275df4d Mon Sep 17 00:00:00 2001 From: Oliver Schneider Date: Tue, 6 Mar 2018 11:16:20 +0100 Subject: [PATCH 3/5] Don't error on long const evals --- text/0000-const-looping.md | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/text/0000-const-looping.md b/text/0000-const-looping.md index 136a5697b95..95c7b1fc7e3 100644 --- a/text/0000-const-looping.md +++ b/text/0000-const-looping.md @@ -78,18 +78,15 @@ const fn fib(n: u128) -> u128 { A loop in MIR is a cyclic graph of `BasicBlock`s. Evaluating such a loop is no different from evaluating a linear sequence of `BasicBlock`s, except that termination is not guaranteed. To ensure that the compiler never hangs -indefinitely, we count the number of terminators processed and once we reach a -fixed limit, we report an error mentioning that we aborted constant evaluation, -because we could not guarantee that it'll terminate. +indefinitely, we count the number of terminators processed and whenever we reach +a fixed limit, we report a lint mentioning that we cannot guarantee that the +evaluation will terminate and reset the counter to zero. This lint should recur +in a non-annoying amount of time (e.g. at least 30 seconds between occurrences). # Drawbacks [drawbacks]: #drawbacks -* Loops are not guaranteed to terminate - * We catch this already by having a maximum number of basic blocks that we - can evaluate. -* A guaranteed to terminate, non looping constant might trigger the limit, if it - has too much code. +* Infinite loops will hang the compiler if the lint is not denied # Rationale and alternatives [alternatives]: #alternatives @@ -98,3 +95,9 @@ because we could not guarantee that it'll terminate. # Unresolved questions [unresolved]: #unresolved-questions + +* Should we add a true recursion check that hashes the interpreter state and + detects if it has reached the same state again? + * This will slow down const evaluation enormously and for complex iterations + is essentially useless because it'll take forever (e.g. counting from 0 to + `u64::max_value()`) From ee780e910199d6b229747c556c7ba919fb7d3375 Mon Sep 17 00:00:00 2001 From: Oliver Schneider Date: Mon, 14 May 2018 18:51:29 +0200 Subject: [PATCH 4/5] Address nits --- text/0000-const-looping.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/text/0000-const-looping.md b/text/0000-const-looping.md index 95c7b1fc7e3..b0bf991864a 100644 --- a/text/0000-const-looping.md +++ b/text/0000-const-looping.md @@ -82,11 +82,15 @@ indefinitely, we count the number of terminators processed and whenever we reach a fixed limit, we report a lint mentioning that we cannot guarantee that the evaluation will terminate and reset the counter to zero. This lint should recur in a non-annoying amount of time (e.g. at least 30 seconds between occurrences). +This means that there's an internal deterministic counter (for the terminators) and +a timestamp of the last (if any) loop warning emission. Both the counter needs to reach +its limit and 30 seconds have to have passed since the last warning emission in order +for a new warning to be emitted. # Drawbacks [drawbacks]: #drawbacks -* Infinite loops will hang the compiler if the lint is not denied +* Infinite loops will cause the compiler to never finish if the lint is not denied # Rationale and alternatives [alternatives]: #alternatives From e4ac9b0ddcfedbd693afc07a27406c1140cc4449 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Mon, 2 Jul 2018 20:32:27 +0200 Subject: [PATCH 5/5] RFC 2344 --- text/{0000-const-looping.md => 2344-const-looping.md} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename text/{0000-const-looping.md => 2344-const-looping.md} (94%) diff --git a/text/0000-const-looping.md b/text/2344-const-looping.md similarity index 94% rename from text/0000-const-looping.md rename to text/2344-const-looping.md index b0bf991864a..5e9e1a159f4 100644 --- a/text/0000-const-looping.md +++ b/text/2344-const-looping.md @@ -1,7 +1,7 @@ -- Feature Name: const_looping +- Feature Name: `const_looping` - Start Date: 2018-02-18 -- RFC PR: (leave this empty) -- Rust Issue: (leave this empty) +- RFC PR: [rust-lang/rfcs#2344](https://github.com/rust-lang/rfcs/pull/2344) +- Rust Issue: [rust-lang/rust#52000](https://github.com/rust-lang/rust/issues/52000) # Summary [summary]: #summary