Kotlin study (9) : inline functions

There are some run-time efficiency costs associated with using higher-order functions: each function is an object, and a closure is captured. Closure the scope of variables that will be accessed in the function body. Memory allocation (for function objects and classes) and virtual calls introduce runtime overhead.

But in many cases you can eliminate such overhead by inlining lambda expressions. The following function is a good example of this situation. The lock() function can easily be inlined at the invocation. Consider the following:

lock(l) { foo() }
Copy the code

The compiler does not create a function object for the argument and generate a call. Instead, the compiler generates the following code:

l.lock()
try {
    foo()
} finally {
    l.unlock()
}
Copy the code

To get the compiler to do this, we need to mark the lock() function with an inline modifier:

inline fun <T> lock(lock: Lock, body: () -> T){: T... }Copy the code

The inline modifier affects the function itself and the lambda expression passed to it: all of these are inlined to the call.

Inlining can result in an increase in generated code. But if you do it right (and avoid inlining excessively large functions), performance can improve, especially at “megamorphic” calls in loops.

noinline

If you do not want all lambda expression arguments passed to an inline function to be inline, you can use the noinline modifier to mark function arguments that you do not want inline:

inline fun foo(inlined: () -> Unit.noinline notInlined: () -> Unit) { …… }
Copy the code

Lambda expressions that can be inlined can only be called inside an inline function or passed as arguments that can be inlined. But noinline lambda expressions can be manipulated in any way you like, including stored in fields, or passed in.

If an inline function has no inlinable function arguments and no materialized type arguments, the compiler generates a warning because inlining such a function is probably not beneficial (if you are sure you want to inline, turn this warning off with the @suppress (“NOTHING_TO_INLINE”) annotation).

Nonlocal return

In Kotlin, you can only exit with a normal, unqualified return for named or anonymous functions. To exit a lambda expression, use a tag. A naked return is disallowed inside a lambda expression because a lambda expression cannot make the containing function return:

fun ordinaryFunction(block: () -> Unit) {
    println("hi!")}//sampleStart
fun foo(a) {
    ordinaryFunction {
        return // Error: 'foo' cannot be returned here}}//sampleEnd
fun main(a) {
    foo()
}
Copy the code

But if the function passed by the lambda expression is inline, the return can be inline as well. So it goes like this:

inline fun inlined(block: () -> Unit) {
    println("hi!")}//sampleStart
fun foo(a) {
    inlined {
        return // OK: The lambda expression is inline}}//sampleEnd
fun main(a) {
    foo()
}
Copy the code

Such a return, which is in a lambda expression but exits the function that contains it, is called a nonlocal return. This construct is commonly used in loops, where the inline function usually contains:

fun hasZeros(ints: List<Int>): Boolean {
    ints.forEach {
        if (it == 0) return true Return from hasZeros
    }
    return false
}
Copy the code

Note that some inline functions may call lambda expression arguments passed to them not directly from the function body, but from another execution context, such as local objects or nested functions. In this case, non-local control flow is also not allowed in the lambda expression. To indicate that lambda arguments to inline functions cannot be returned nonlocally, mark lambda arguments with the Crossinline modifier:

inline fun f(crossinline body: () -> Unit) {
    val f = object: Runnable {
        override fun run(a) = body()
    }
    / /...
}
Copy the code

Break and continue are not yet available in inline lambda expressions, but we plan to support them as well.

Externalized type parameters

Sometimes you need to access a type passed as a parameter:

fun <T> TreeNode.findParentOfType(clazz: Class<T>): T? {
    var p = parent
    while(p ! =null && !clazz.isInstance(p)) {
        p = p.parent
    }
    @Suppress("UNCHECKED_CAST")
    return p as T?
}
Copy the code

Here you walk up a tree and check that each node is of a specific type. None of this is a problem, but the invocation is not very elegant:

treeNode.findParentOfType(MyTreeNode::class.java)
Copy the code

A better solution is to simply pass a type to the function and call it as follows:

treeNode.findParentOfType<MyTreeNode>()
Copy the code

To do this, inline functions support externalized type arguments, so they can be written like this:

inline fun <reified T> TreeNode.findParentOfType(a): T? {
    var p = parent
    while(p ! =null && p !is T) {
        p = p.parent
    }
    return p as T?
}
Copy the code

The code above uses the reified modifier to qualify a type parameter so that it can be accessed inside the function, almost as if it were a normal class. Since the function is inline, no reflection is required, normal operators such as! Both IS and AS are now available. Alternatively, you can call the function as shown above: mytree.findparentofType

().

Although reflection may not be needed in many cases, it can still be used for an externalized type parameter:

inline fun <reified T> membersOf(a) = T::class.members

fun main(s: Array<String>) {
    println(membersOf<StringBuilder>().joinToString("\n"))}Copy the code

Ordinary functions (not marked inline) cannot have externalized arguments. A type that does not have a runtime representation (such as an unmaterialized type parameter or a fictitious type like Nothing) cannot be used as an argument to a materialized type parameter.

Inline attribute

The inline modifier can be used with accessors for attributes that have no background field. You can annotate individual property accessors:

val foo: Foo
    inline get() = Foo()

var bar: Bar
    get() =...inline set(v) { …… }
Copy the code

You can also annotate the entire property by marking both of its accessors inline:

inline var bar: Bar
    get() =...set(v) { …… }
Copy the code

At the point of invocation, inline accessors are inlined as inline functions.

Limitations on inline functions in the public API

An inline function is considered a module-level public API when it is part of a public or protected declaration rather than a private or internal one. It can be called in other modules, and such calls can also be inlined at the invocation.

This introduces some binary compatibility risks when a module makes such a change-declare an inline function but the module that calls it does not recompile after its modification.

To eliminate the risk of incompatibility introduced by changes to non-public APIS, non-public declarations are not allowed in the body of public API inline functions; that is, private and internal declarations and their parts are not allowed.

An internal declaration can be annotated by @publishedAPI, which allows it to be used in public API inline functions. When an internal inline function is tagged with @publishedAPI, its body is also checked as if it were a public function.