Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Kotlin's crossinline keyword

Tags:

kotlin

I have read this question but I have a more fundamental question regarding the crossinline keyword. I'm not really sure what problem it is solving and how it solves it.

From the Kotlin Docs,

Note that some inline functions may call the lambdas passed to them as parameters not directly from the function body, but from another execution context, such as a local object or a nested function. In such cases, non-local control flow is also not allowed in the lambdas. To indicate that, the lambda parameter needs to be marked with the crossinline modifier:

[Emphasis added]

This statement is ambiguous to me. 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.

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?

In short, I have trouble figuring out what the context for using this really is, what using it communicates to clients, and what the expected results of applying this keyword are.

like image 726
melston Avatar asked Aug 18 '17 16:08

melston


People also ask

What is Crossinline in Kotlin?

The crossinline Effect. In Kotlin, we can only use a normal, unqualified return to exit a named function, an anonymous function, or an inline function. In order to exit from a lambda, we must use a label (as in return@label).

How does the reified keyword in Kotlin work?

"reified" is a special type of keyword that helps Kotlin developers to access the information related to a class at runtime. "reified" can only be used with inline functions. When "reified" keyword is used, the compiler copies the function's bytecode to every section of the code where the function has been called.

What is inline function in Kotlin Mindorks?

Inline function instruct compiler to insert complete body of the function wherever that function got used in the code. Advantages of inline function are as follows: Function call overhead doesn't occur. It also saves the overhead of push/pop variables on the stack when the function is called.

What is inline function in C++?

Inline function in C++ is an enhancement feature that improves the execution time and speed of the program. The main advantage of inline functions is that you can use them with C++ classes as well.


2 Answers

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.

like image 94
hotkey Avatar answered Oct 10 '22 00:10

hotkey


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.

like image 36
Yogesh Umesh Vaity Avatar answered Oct 09 '22 22:10

Yogesh Umesh Vaity