Detailed Explanation of return Statement in lambda of Kotlin Basic Learning

  • 2021-09-16 08:05:34
  • OfStack

Preface

When we fell in love with lambda and used it widely, I think everyone was severely molested by return statement in lambda, so today we need to uncover the mystery of return in lambda.

Let's start with an example:


fun demo() {
 val indexes = arrayOf(1, 2, 3, 4, 5, 6, 7)
 indexes.forEach {
  if (it > 5) {
   return
  }
  println(it)
 }
 println("End")
}

As we expected, after calling demo, it should output:

1
2
3
4
5
End

Is this really the case? You can run the above code under 1, and its actual output is:

1
2
3
4
5

What the hell? Was that End stolen at runtime? No, to explain why, let's look at the definition of forEach under 1:


public inline fun <T> Array<out T>.forEach(action: (T) -> Unit): Unit {
 for (element in this) action(element)
}

From the definition, we can see that the forEach function is defined as an inline function, and according to the inline function processing mechanism with lambda (please refer to: Kotlin: Some understandings about inline functions), our demo will eventually be compiled as:


fun demo() {
 val indexes = arrayOf(1, 2, 3, 4, 5, 6, 7)
 for (element in indexes) {
  if (element > 5) {
   return
  }
  println(element)
 }
 println("End")
}

From the final compiled code, we can clearly find that the final End was not stolen, but because the demo function returned in advance when the judgment conditions were met, so the return is called non-local return with a high-end 1-point statement.

What if we want return to return from forEach (that is, lambda)? Let's first modify the call to forEach under 1:


indexes.forEach {
 if (it > 5) {
  return@forEach
 }
 println(it)
}

Run 1 demo again:

1
2
3
4
5
End

At this time, the output is just like our expectation, which is amazing, isn't it? All we need to do is add a @ forEach after return, which is called tag return (or local return). Its complete syntax is as follows:


indexes.forEach label@ {
 if (it > 5) {
  return@label
 }
 println(it)
}

If we omit the definition of label after forEach, the default label is the function name with lambda as the parameter (in this case forEach).

That's all the return statement in lambda, isn't it simple? Then it's time to have fun? Don't worry, here are one more precautions, such as:


fun doSomething(action: () -> Unit) {
 action()
}

inline fun doOtherThing(action: () -> Unit) {
 action()
}

fun main(args: Array<String>) {
 doSomething {
  return
 }
}

fun main(args: Array<String>) {
 var action = {
  return
 }
 doOtherThing(action)
}

What kind of output will you get when you run the above example? Sorry, this call will not give you a chance to run because of compilation failure.

As we know from the above discussion, The nonlocal return is returned from the function where the lambda call point is located, Therefore, this requires that the return statement in lambda can only appear in the inline function and the lambda expression is passed directly to the function in the form of parameters. Other situations are not allowed because lambda can be bound to a variable to continue to use after the function returns (such as closure), and return is too late at this time.

Ok, that's all for the lambda control flow. Finally, I wish you all happiness coding ^ _ ^

Summarize


Related articles: