|
| 1 | +# Infinite loop |
| 2 | + |
| 3 | +* **Type**: Design proposal |
| 4 | +* **Author**: Vadim Mishenev |
| 5 | +* **Contributors**: Roman Elizarov |
| 6 | +* **Status**: In Progress |
| 7 | +* **Prototype**: In Progress (inferring `Nothing` is implemented) |
| 8 | +* **Issue**: [KT-27970](https://youtrack.jetbrains.com/issue/KT-27970/Support-an-infinite-for-loop) |
| 9 | +* **Discussion**: [#364](https://github.com/Kotlin/KEEP/issues/364) |
| 10 | + |
| 11 | +## Summary |
| 12 | + |
| 13 | +This KEEP introduces a new expression `for { ... }` to indicate an [infinite loop](https://en.wikipedia.org/wiki/Infinite_loop) (also known as a “while true”). |
| 14 | + |
| 15 | +## Motivation |
| 16 | + |
| 17 | +Kotlin leads to be laconic programming language, and an infinite loop `while(true) { ... }` (rare `do { ... } while(true)`) might be expressed more concisely. |
| 18 | +Besides, a dedicated expression makes an infinite loop immediately understandable. |
| 19 | +This usually results in more readable code. |
| 20 | + |
| 21 | +### Use-cases |
| 22 | + |
| 23 | +Infinite loops is an idiom that is widely used in Kotlin programming. |
| 24 | +In Kotlin the percentage of `while(true)` among all written `while` loops is 19% (in 1.2M repositories from the BigCode tool). |
| 25 | + |
| 26 | +- Infinite loops are widely used to monitor user input or device activity. |
| 27 | +The idiomatic approach to reading all the lines from the input stream until it is over (until `readLine` function returns null): |
| 28 | + |
| 29 | + ```kotlin |
| 30 | + while(true) { |
| 31 | + val line = input.readlnOrNull() ?: break |
| 32 | + // process line |
| 33 | + } |
| 34 | + |
| 35 | + ``` |
| 36 | + Or the while loop can be used for the main game frame which continues to get executed until the user or the game selects some other event. |
| 37 | + |
| 38 | +- Infinite loops also appear quite often in concurrent programming with coroutines, because various concurrent background processes are often conveniently represented as an infinite loop doing something until cancelled. |
| 39 | + |
| 40 | +- It is often used along with exit conditions / jump expressions at the middle of a body loop. |
| 41 | + |
| 42 | + with `when` condition: |
| 43 | + |
| 44 | + ```kotlin |
| 45 | + while(true) { |
| 46 | + when(await().resultCode) { |
| 47 | + RESULT_OK -> break |
| 48 | + RESULT_CANCELED -> { |
| 49 | + finish() |
| 50 | + return false |
| 51 | + } |
| 52 | + else -> continue |
| 53 | + } |
| 54 | + } |
| 55 | + ``` |
| 56 | + or inside `try-catch` block: |
| 57 | + |
| 58 | + ```kotlin |
| 59 | + while(true) { |
| 60 | + try { |
| 61 | + // repeated process |
| 62 | + } catch(e: InterruptedException) { |
| 63 | + break; |
| 64 | + } |
| 65 | + } |
| 66 | + ``` |
| 67 | + |
| 68 | + |
| 69 | +The list of use cases is not exhaustive. |
| 70 | +In general, an infinite loop is a common form, and usual loops (`while`, `do-while`) with a condition can be rewritten with it and vice versa. |
| 71 | + |
| 72 | + |
| 73 | +### Other languages |
| 74 | + |
| 75 | +- Go (Golang) has `for { ... }` - loop. Probably `for` stems from `repeat forever`. |
| 76 | + |
| 77 | +- In Rust there is `loop { ... }`. |
| 78 | + |
| 79 | +- In C# there was a [proposal](https://github.com/dotnet/csharplang/issues/2475), but the discussion was shut down. |
| 80 | + |
| 81 | + **Summary** |
| 82 | + |
| 83 | + The community does not want to encourage the use of infinite loops. In their opinion, it is better to have loops with a condition or flag that can explain a reason of termination. |
| 84 | + |
| 85 | + `while{ .. }` looks like a user accidentally forgot to type the condition or accidentally removes it (say cut/paste instead of copy/paste) and ends up with valid code that represents an infinite loop. |
| 86 | + |
| 87 | +and so on (Ada, Ruby, Fortran). |
| 88 | + |
| 89 | +Remarkably, Rust, Go, Ruby do not have `do-while` loop. So infinite loop can be a way to write it in these languages. |
| 90 | + |
| 91 | + |
| 92 | +## Design |
| 93 | + |
| 94 | +The proposal is to support infinite loop via the concise `for { ... }` syntactic construction without parameters or conditions. The curly braces `{ ... }` are required and can not be omitted. |
| 95 | +It should be used as expression with the result type `Nothing`, but if a loop has a `break` expression, the result type of expression should be `Unit`. |
| 96 | + |
| 97 | + |
| 98 | +## Statement or expression |
| 99 | + |
| 100 | +### Other languages |
| 101 | + |
| 102 | +In Golang infinite loop `for { ... }` is a statement. (see [The Go Language Specification: for clause](https://go.dev/ref/spec#ForClause)). |
| 103 | + |
| 104 | +On the other hand, in Rust `loop { ... }` is an expression. (see [The Rust Reference: infinite loops](https://doc.rust-lang.org/reference/expressions/loop-expr.html#infinite-loops)) |
| 105 | +A loop expression without an associated break expression has type `!` ([Never type](https://doc.rust-lang.org/reference/types/never.html)). |
| 106 | +Meanwhile, `break` expressions of Rust can have a [loop value](https://doc.rust-lang.org/reference/expressions/loop-expr.html#break-and-loop-values) only for infinite loops, e.g. |
| 107 | + |
| 108 | +```rust |
| 109 | +let result = loop { |
| 110 | + if b > 10 { |
| 111 | + break b; |
| 112 | + } |
| 113 | + let c = a + b; |
| 114 | + a = b; |
| 115 | + b = c; |
| 116 | +}; |
| 117 | +``` |
| 118 | + |
| 119 | +### Type inference in Kotlin |
| 120 | + |
| 121 | +Currently, in Kotlin infinite loops cannot be properly used inside scope functions. The following code does not compile due to type mismatch, since `while` is not an expression and the resulting type of run coerces to Unit (see [KT-25023](https://youtrack.jetbrains.com/issue/KT-25023/Infinite-loops-in-lambdas-containing-returnlabel-dont-coerce-to-any-type)): |
| 122 | + |
| 123 | +```kotlin |
| 124 | +fun foo(): Nothing = // Should be compiled, but Error: Type mismatch: inferred type is Unit but Nothing was expected |
| 125 | + run { |
| 126 | + while (true) { |
| 127 | + doSomething() |
| 128 | + } |
| 129 | + } |
| 130 | +``` |
| 131 | +Infinite loop shall be an expression of `Nothing` type (similar to `throw` to mark code locations that can never be reached) so this code compiles. |
| 132 | +Meanwhile, the type should be `Unit` if a loop contains `break`. |
| 133 | +Despite `return` breaking a loop, it does not make code after an infinite loop reachable so the expression type can be `Nothing`. It allows to compile the following code: |
| 134 | + |
| 135 | +```kotlin |
| 136 | +val x = run<Int> { |
| 137 | + while (true) { |
| 138 | + return@run 1 // Error: Type mismatch: inferred type is Unit but Int was expected |
| 139 | + } |
| 140 | + } |
| 141 | +``` |
| 142 | +Moreover, the proposed infinite loop `for { ... }` with `Nothing`/`Unit` types is backwards compatible with old code. |
| 143 | + |
| 144 | +### Should existing loops in Kotlin be expresions? |
| 145 | + |
| 146 | +IDE can have an intention to transform loops with `true` condition into proposed infinite loops. It could make sense to make existing loops with a condition (`do-while`, `while`) expressions. |
| 147 | +The existing loops would be used as an expression that allows, for example: |
| 148 | +```kotlin |
| 149 | +var v = getSmth() ?: while( condition ) { } // Error: While is not an expression, and only expressions are allowed here |
| 150 | +``` |
| 151 | + |
| 152 | +But it breaks backward compatibility with already written code, for example: |
| 153 | + |
| 154 | +```kotlin |
| 155 | +fun foo() = run { |
| 156 | + while (true) { |
| 157 | + doSomething() |
| 158 | + } |
| 159 | +} |
| 160 | +``` |
| 161 | +After this change, `foo` would have `Nothing` type instead of `Unit`. Also, it would cause the compiler error `'Nothing' return type needs to be specified explicitly`. |
| 162 | +So the loops with a condition should be left statements. |
| 163 | + |
| 164 | + |
| 165 | + |
| 166 | +### Functions with expression body |
| 167 | + |
| 168 | + `return` is frequently used to exit from an infinite loop as well, but `return`s are not allowed for functions with expression body in Kotlin, e.g. |
| 169 | + |
| 170 | +```kotlin |
| 171 | +fun test() = while (true) { |
| 172 | + return 42 // Error: Returns are not allowed for functions with expression body. Use block body in '{...}' |
| 173 | + } |
| 174 | +``` |
| 175 | +This problem can be solved by `break` with a loop value like in Rust. But it deserves another proposal. |
| 176 | + |
| 177 | +## Feature versus stdlib function |
| 178 | + |
| 179 | +Infinite loops can be implemented via a function (like `repeat` in StdLib), but it will not support `break`/`continue`. So this feature is the shortest way to support it. |
| 180 | +[KT-19748](https://youtrack.jetbrains.com/issue/KT-19748/Provide-some-sort-of-break-continue-like-mechanism-for-loop-like-functions) solves this problem for a such function and loop-like functions (`forEach`, `filter`, etc). But there are some disadvantages of this feature: |
| 181 | + |
| 182 | +- It seems to be very specified only for stdlib's functions. |
| 183 | +- Existed local `return` can become misleading with a labelled `break`/`continue` expression. A user can expect a local `return` should exit a loop-like function at all. It might need to change the behavior of local `return` for loop-like function. |
| 184 | + |
| 185 | + |
| 186 | + ```kotlin |
| 187 | + (0..9).forEach { |
| 188 | + if (it == 0) return@forEach // currently, it has the same behavior as `continue@forEach` |
| 189 | + println(it) |
| 190 | + } |
| 191 | + ``` |
| 192 | + |
| 193 | + |
| 194 | +- For a function of infinite loop inside a loop (`for`, `while`), it might require extra labels that make code verbose. |
| 195 | +See [KEEP-326](https://github.com/Kotlin/KEEP/issues/326): |
| 196 | +an unlabeled `break`/`continue` goes to the innermost enclosing block that is clearly marked with `for`, `do`, or `while` hard keywords: |
| 197 | + |
| 198 | + ```kotlin |
| 199 | + for (elem in 1..10) { |
| 200 | + (1..10).someInfiniteLoopFunction { |
| 201 | + println(it) |
| 202 | + if (it == 5) break@someInfiniteLoopFunction |
| 203 | + } |
| 204 | + } |
| 205 | + ``` |
| 206 | + |
| 207 | +- The implementation of the feature is more complicated than infinite loops. |
| 208 | + |
| 209 | + |
| 210 | +## Which keyword is used for infinite loops? |
| 211 | + |
| 212 | +Main candidates of the keyword: |
| 213 | + |
| 214 | +- `while { ... } ` |
| 215 | +- `for { ... }` |
| 216 | + The Golang (Go) uses it. Probably `for` is shortened to `forever`. |
| 217 | +- `do { ... }` But `do {} while(...) {}` can be ambiguous so for a user. A user should check the end of a loop to differ `do-while` and `do` loops. |
| 218 | +- `loop { ... }` |
| 219 | + This keyword is used in Rust for infinite loops. |
| 220 | +- `repeat { ... }` |
| 221 | + |
| 222 | + |
| 223 | +The plus of using words `while`, `do`, `for` is that they are existing keywords. Users are already familiar with their semantics. |
| 224 | +Otherwise, introducing a new keyword increases syntax complexity of the language. |
| 225 | +Also, it can break backward compatibility since old code can have a user function with the same name as the keyword. |
| 226 | + |
| 227 | +`while { ... } ` can look like that a user forgot to write a condition. At the same time, unlike othe languages, Kotlin does not have a free-form condition for `for` loop. |
| 228 | + |
| 229 | + |
| 230 | +The existing keywords `for` and `while` require `{ ... }` for an infinite loop body. |
| 231 | +Contrariwise, for example, the following code becomes ambiguous: |
| 232 | + |
| 233 | +```kotlin |
| 234 | +for (x in xs); |
| 235 | +``` |
| 236 | +It can be treated either as a for loop with a loop variable `x` and an empty body or as an infinite loop which evaluates expression `(x in xs)`. |
| 237 | + |
| 238 | + |
| 239 | +To sum up, `for` is the best choice for an infinite loop. It does not require intruduction a new keyword and do not have the problem with forgotten condition like `while`. |
| 240 | +It seems to read nicely for the case of an infinite loop, changing the motivating example from `Use-cases` section to: |
| 241 | + |
| 242 | +```kotlin |
| 243 | +for { |
| 244 | + val line = input.readLine() ?: break |
| 245 | + // process line |
| 246 | +} |
| 247 | + |
| 248 | +``` |
| 249 | +Additionally, factoring this form both away from and back to having a condition is natural. |
| 250 | + |
| 251 | + |
0 commit comments