Skip to content

Commit 824c667

Browse files
vmishenevnikitabobko
authored andcommitted
Add proposal for infinite loop
closes #363
1 parent 998955b commit 824c667

File tree

1 file changed

+251
-0
lines changed

1 file changed

+251
-0
lines changed
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
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

Comments
 (0)