Search code examples
androidlambdakotlininline

Kotlin - Illegal usage of inline parameter callback


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()
                                )))
                            }
                }
            })
        }
    }
}

Solution

  • 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.