Kotlin - Illegal usage of inline parameter callback
Asked Answered
H

1

1

I'm converting my function having lambda as parameter into inline function for performance improvement.

I have list of lambda of type MutableList<(Authenticate) -> Unit> variable as data member in class. When I try to adding lambda parameter into the list.

Kotlin compiler says:

Illegal usage of inline parameter callback

Here is the code

// Some code skipped
object Odoo {

    val pendingAuthenticateCallbacks = mutableListOf<(Authenticate) -> Unit>()

    inline fun authenticate(
        login: String, password: String, database: String,
        quick: Boolean = false, crossinline callback: Authenticate.() -> Unit
    ) {
        // Following statement has error saying
        // Illegal usage of inline parameter callback. add 'noinline' modifier to parameter declaration.
        pendingAuthenticateCallbacks += callback
        // Error in above statement

        if (pendingAuthenticateCallbacks.size == 1) {
            // Retrofit2 Object boxing code skipped
            val call = request.authenticate(requestBody)
            call.enqueue(object : Callback<Authenticate> {
                override fun onFailure(call: Call<Authenticate>, t: Throwable) {
                    (pendingAuthenticateCallbacks.size - 1 downTo 0)
                            .map { pendingAuthenticateCallbacks.removeAt(it) }
                            .forEach {
                                it(Authenticate(httpError = HttpError(
                                        Int.MAX_VALUE,
                                        t.message!!
                                )))
                            }
                }

                override fun onResponse(call: Call<Authenticate>, response: Response<Authenticate>) {
                    (pendingAuthenticateCallbacks.size - 1 downTo 0)
                            .map { pendingAuthenticateCallbacks.removeAt(it) }
                            .forEach {
                                it(Authenticate(httpError = HttpError(
                                        response.code(),
                                        response.errorBody()!!.string()
                                )))
                            }
                }
            })
        }
    }
}
Hypergolic answered 21/11, 2017 at 18:5 Comment(0)
W
14

Inlining inserts the code in the lambda directly into the call site, which removes the overhead of having a function object.

For example, this roughly results in main here:

fun withLambda(lambda: () -> Unit) {
    lambda()
}

inline fun inlinedLambda(lambda: () -> Unit) {
    lambda()
}

fun main(args: Array<String>) {
    withLambda { println("Hello, world") }
    inlinedLambda { println("Hello, world") }
}

being converted to this:

fun main(args: Array<String>) {
    withLambda { println("Hello, world") }
    println("Hello, world") // <- Directly inserted!
}

If you have

pendingAuthenticateCallbacks += callback

This is impossible because callback must be an object in order for it to be added to the list.

You need to add the noinline modifier.

A rough approximation would be to say that an inlined lambda cannot be treated as an object, as it doesn't really exist as an object. It is used directly instead of being created as an object.

Of course, you could create a containing lambda:

pendingAuthenticateCallbacks += { callback() } // Not a good idea

but this would entirely defeat the point of inlining (don't do this!).

However, making the parameter noinline would mean your method now has zero lambda parameters that can be inlined, so you might as well just remove the inline modifier as performance benefit would be minimal.

The compiler should recognize this:

Note that if an inline function has no inlinable function parameters and no reified type parameters, the compiler will issue a warning, since inlining such functions is very unlikely to be beneficial.


The main reason for inlining methods is for performance when using lambdas and for reified generic type parameters. As of Kotlin 1.1, it is also possible to have an inline property accessor for properties without a backing field.


In short, if you have no lambda parameters (or no reified type parameters, in which case you must), it is usually pointless to mark a function as inline.

Welfarism answered 21/11, 2017 at 18:51 Comment(5)
Thanks for giving awesome explanation. So, I need to remove inline keyword from function and add noinline keyword to callback parameter. right?Hypergolic
@Ka7Im1011 noinline results in a compile error if the function is not inline, so you just need to remove the inline keyword from the function.Welfarism
Sorry Your answer updated. So, I can achieve better performance using pendingAuthenticateCallbacks += { callback() } with inline function. right?Hypergolic
@Ka7Im1011 I meant to add that of an example of what is possible. I should clarify this (I will edit it) but no, it would be better to just make the function not inline.Welfarism
again Thanks for this detailed explanation.Hypergolic

© 2022 - 2024 — McMap. All rights reserved.