https://kotlinlang.org logo
#getting-started
Title
# getting-started
j

Jason5lee

01/06/2022, 1:54 PM
In the first peace of code, it said that the
throw
is unreachable. But after I removed it there is an compile error. Is it a known issue?
k

Kirill Grouchnikov

01/06/2022, 1:56 PM
The first piece marks
throw
as unreachable because you have an infinite loop. The second piece says that since you never return and never throw, this function can not return the promised
Int
👌 1
j

Jason5lee

01/06/2022, 1:57 PM
Then why the first piece has no compile error? It can not return the promised
Int
either.
k

Kirill Grouchnikov

01/06/2022, 1:58 PM
It's because of the
throw
that is considered to be a "valid" escape from such a function. Except that the
throw
can't be reached either.
☝️ 1
j

Jason5lee

01/06/2022, 1:58 PM
btw the code compiles once
run
is removed.
e

ephemient

01/06/2022, 2:12 PM
throw
expression has type
Nothing
, loop has
Unit
type. although arguably an infinite loop should also have type
Nothing
, Kotlin compiler can't infer that now. https://youtrack.jetbrains.com/issue/KT-25023
j

Jason5lee

01/06/2022, 2:49 PM
Fair. Just not using run solves it, not a big deal.
y

Youssef Shoaib [MOD]

01/06/2022, 4:22 PM
Yeah run makes the compiler consider the type of the lambda to be returning Unit
e

ephemient

01/11/2022, 2:23 AM
IMO Rust gets this right by having a separate control structure for infinite loops, which you can sort of emulate with
Copy code
inline fun loop(block: () -> Unit): Nothing {
    while (true) block()
}
but the Rust version also allows for
break value
, which I don't see an elegant way to implement in Kotlin
you could get the interface with
Copy code
interface LoopScope<in T> {
    fun emit(value: T): Nothing
}

class Return(val value: Any?) : Throwable("", null, false, false)

inline fun <T> loop(@BuilderInference block: LoopScope<T>.() -> Unit): T {
    val loopScope = object : LoopScope<T> {
        override fun emit(value: T): Nothing {
            throw Return(value)
        }
    }
    try {
        while (true) loopScope.block()
    } catch (e: Return) {
        @Suppress("UNCHECKED_CAST")
        return e.value as T
    }
}
or
Copy code
suspend fun <T> loop(@BuilderInference block: suspend FlowCollector<T>.() -> Unit): T = flow {
    while (true) block()
}.first()
which can both be used like
Copy code
assert(loop { if (Random.nextInt(10) == 0) emit(true) })
but both implementations have pretty annoying downsides
y

Youssef Shoaib [MOD]

01/20/2022, 3:58 PM
You could do:
Copy code
inline fun loop(block: () -> Unit): Nothing {
    while (true) block()
}
fun main() {
    val isZero = run {
        loop {
            if (Random.nextInt(10) == 0) return@run true
        }
    }
}
In Kotlin, whenever you find the notion of "breaking out" of some scope, one of the first things you can try is just to nest that scope inside a run. Alternatively, similar to your previous example but simplified:
Copy code
typealias Emit<T> = (T) -> Unit
private object UNINITIALIZED
inline fun <T> loop(block: (Emit) -> Unit): T {
    val returnValue: Any? = UNINITIALIZED
    while (returnValue == UNINITIALIZED) block { returnValue = it }
    return returnValue as T
}
Note however that the returnValue setter won't be inlined because the compiler can't inline higher-higher-order functions, if you will.
e

ephemient

01/20/2022, 5:51 PM
the second isn't right:
Copy code
loop { ret ->
    ret(1)
    throw
}
throws instead of returning
🤔 1