Return and jump

Kotlin has three structured jump expressions:

  • The return. The default is to return from the function that most directly surrounds it or from an anonymous function.

  • Break. Terminate the loop that most directly surrounds it.

  • The continue. Continue the next cycle that most directly surrounds it.

All of these expressions can be used as part of a larger expression:



val s = person.name ?: return

These expressions are of type Nothing.

Break and Continue labels

Any expression in Kotlin can be labeled with a label. Tags are formatted as identifiers followed by @ signs, such as ABC @ and fooBar@ are valid tags (see syntax). To label an expression, we simply label it.



loop@ for (i in 1.. 100) {
/ /...
}

Now, we can restrict break or continue with tags:



loop@ for (i in 1.. 100) {
for (j in 1.. 100) {
If (...) break@loop
    }
}

The break restricted by the tag jumps to the execution point just after the loop specified by the tag. Continue continues the next iteration of the loop specified by the tag.

Back to the tag

Kotlin has function literals, local functions, and object expressions. So Kotlin’s functions can be nested. The tag-restricted return allows us to return from the outer function. One of the most important uses is to return from lambda expressions. Think back to when we wrote this:



fun foo() {
    listOf(1, 2, 3, 4, 5).forEach {
If (it == 3) return // non-locally returns directly to the caller of foo()
        print(it)
    }
    println("this point is unreachable")
}

12

Target platform: JVMRunning on kotlin v. 1.5.20

This return expression returns from foo, the function that most directly surrounds it. (Note that this nonlocal return only supports lambda expressions passed to inline functions.) If we need to return from a lambda expression, we must label it and restrict the return.



fun foo() {
    listOf(1, 2, 3, 4, 5).forEach lit@{
If (it == 3) return@lit // the local returns to the caller of the lambda expression, namely the forEach loop
        print(it)
    }
    print(" done with explicit label")
}

1245 done with explicit label

Target platform: JVMRunning on kotlin v. 1.5.20

Now, it only returns from the lambda expression. It is often more convenient to use implicit labels. The label has the same name as the function that accepts the lambda.



fun foo() {
    listOf(1, 2, 3, 4, 5).forEach {
If (it == 3) return@forEach // the local returns to the caller of the lambda expression, namely the forEach loop
        print(it)
    }
    print(" done with implicit label")
}

1245 done with implicit label

Target platform: JVMRunning on kotlin v. 1.5.20

Alternatively, we can replace lambda expressions with an anonymous function. A return statement inside an anonymous function is returned from the anonymous function itself



fun foo() {
    listOf(1, 2, 3, 4, 5).forEach(fun(value: Int) {
If (value == 3) return // returns locally to the caller of the anonymous function, i.e. ForEach loop
        print(value)
    })
    print(" done with anonymous function")
}

1245 done with anonymous function

Target platform: JVMRunning on kotlin v. 1.5.20

Note that the local return used in the previous three examples is similar to using continue in a regular loop. There is no direct equivalent of break, but it can be simulated by adding another layer of nested lambda expressions and returning nonlocally from them:



fun foo() {
    run loop@{
        listOf(1, 2, 3, 4, 5).forEach {
If (it == 3) return@loop // returns nonlocally from the lambda expression passed to run
            print(it)
        }
    }
    print(" done with nested loop")
}

12 done with nested loop

Target platform: JVMRunning on kotlin v. 1.5.20

When a return value is required, the parser preferentially selects the tag-bound return, i.e



return@a 1

Return a labeled expression (@a 1).