Kotlin's crossinline keyword
First, I am having trouble actually picturing what is meant by "such cases". I have a general idea of what the issue is but can't come up with a good example of it.
Here's an example:
interface SomeInterface {
fun someFunction(): Unit
}
inline fun someInterfaceBy(f: () -> Unit): SomeInterface {
return object : SomeInterface {
override fun someFunction() = f()
// ^^^
// Error: Can't inline 'f' here: it may contain non-local returns.
// Add 'crossinline' modifier to parameter declaration 'f'.
}
}
Here, the function that is passed to someInterfaceBy { ... }
is inlined inside an anonymous class implementing SomeInterface
. Compilation of each call-site of someInterfaceBy
produces a new class with a different implementation of someFunction()
.
To see what could go wrong, consider a call of someInterfaceBy { ... }
:
fun foo() {
val i = someInterfaceBy { return }
// do something with `i`
}
Inside the inline lambda, return
is non-local and actually means return from foo
. But since the lambda is not called and leaks into the object i
, return from foo
may be absolutely meaningless: what if i.someFunction()
(and thus the lambda) is called after foo
has already returned or even in a different thread?
Generically, 'such cases' means inline
functions that call their functional parameters not in their own bodies (effectively, i.e. taking other inline functions into account) but inside some other functions they declare, like in non-inline lambdas and anonymous objects.
Second, the phrase "To indicate that," can be read multiple ways. To indicate what? That a particular case is not allowed? That it is allowed? That non-local control flow in a given function definition is (or is not) allowed?
This is exactly how the problem I described above is fixed in the Kotlin language design: whenever an inline
function intends to inline its functional parameter somewhere where it could be not called in-place but stored and called later, the parameter of the inline
function should be marked as crossinline
, indicating that non-local control flow is not allowed in the lambdas passed here.
Problem: non-local return
Let's first understand the problem of non-local return
with a simple example:
fun doSomething() {
println("Before lambda")
doSomethingElse {
println("Inside lambda")
return // This is non-local return
}
println("After lambda")
}
inline fun doSomethingElse(lambda: () -> Unit) {
println("Do something else")
lambda()
}
Non-local return
In the code above, the return
statement is called a non-local return because it's not local to the function in which it is called. This means this return
statement is local to the doSomething()
function and not to the lambda function in which it is called. So, it terminates the current function as well as the outermost function.
Local return
If you just wanted to return from the lambda, you would say return@doSomethingElse
. This is called local return and it is local to the function where it is specified.
Problem
Now the problem here is that the compiler skips the lines after the non-local return
statement. The decompiled bytecode for the doSomething()
looks like following:
public static final void doSomething() {
System.out.println("Before lambda");
System.out.println("Doing something else");
System.out.println("Inside lambda");
}
Notice that there is no statement generated for the line println("After lambda")
. This is because we have the non-local return
inside the lambda and the compiler thinks the code after the return
statement is meaningless.
Solution: crossinline
keyword
crossinline
In such cases (like the problem mentioned above), the solution is to disallow the non-local return
inside the lambda
. To achieve this, we mark the lambda
as crossinline
:
inline fun doSomethingElse(crossinline lambda: () -> Unit) {
println("Doing something else")
lambda()
}
Non-local return
disallowed
When you use the crossinline
keyword, you are telling the compiler, "give me an error, if I accidentally use a non-local return
inside the nested functions or local objects.":
fun doSomething() {
println("Before lambda")
doSomethingElse {
println("Inside lambda")
return // Error: non-local return
return@doSomethingElse // OK: local return
}
println("After lambda")
}
Now the compiler generates the bytecode as expected:
public static final void doSomething() {
System.out.println("Before lambda");
System.out.println("Doing something else");
System.out.println("Inside lambda");
System.out.println("After lambda");
}
That's it! Hope I made it easier to understand.