inline, noinline și crossinline în Kotlin

Funcții și lambda Kotlin 1.0

inline copiază corpul funcției și lambda-urile ei în locul apelului; noinline și crossinline controlează lambda-urile primite.

Sintaxă
inline fun repeta(n: Int, actiune: (Int) -> Unit) { ... }

inline fun f(a: () -> Unit, noinline b: () -> Unit) { ... }

inline fun g(crossinline bloc: () -> Unit) { ... }

Ce face inline, noinline și crossinline

O funcție marcată cu inline nu este apelată normal: compilatorul copiază corpul ei, împreună cu lambda-urile primite, direct în locul apelului. Așa nu se mai creează obiecte pentru lambda, iar codul cu filter, map sau forEach (toate inline) este la fel de rapid ca o buclă scrisă de mână. Avantaj suplimentar: în lambda primită de o funcție inline poți folosi return simplu, care iese din funcția exterioară (non-local return).

noinline se pune pe un parametru lambda care nu trebuie copiat, de obicei pentru că vrei să-l păstrezi într-o variabilă sau să-l trimiți mai departe unei funcții obișnuite. Un parametru inline nu poate fi păstrat ca valoare.

crossinline se pune pe un parametru lambda care va fi apelat din alt context (dintr-un obiect, din altă lambda, dintr-un fir de execuție). Lambda rămâne inline, dar în ea nu mai este permis return simplu, pentru că nu ar avea din ce funcție să iasă.

Exemple

Exemplul 1: Funcție inline și return din lambda

pentruFiecare este inline, deci return din lambda iese direct din cautaPrimulRestant, nu doar din lambda.

inline fun pentruFiecare(elevi: List<Pair<String, Int>>, actiune: (Pair<String, Int>) -> Unit) {
    for (e in elevi) actiune(e)
}

fun cautaPrimulRestant(elevi: List<Pair<String, Int>>): String {
    pentruFiecare(elevi) { (nume, nota) ->
        println("Verific $nume")
        if (nota < 5) return nume
    }
    return "nimeni"
}

fun main() {
    val clasa = listOf("Ana" to 9, "Mihai" to 4, "Ioana" to 3)
    println("Primul restant: ${cautaPrimulRestant(clasa)}")
}

Rezultat

Verific Ana
Verific Mihai
Primul restant: Mihai

Exemplul 2: noinline: lambda păstrată pentru mai târziu

laFinal este pus într-o listă, deci nu poate fi inline. acum este copiat în locul apelului.

val deRulatLaFinal = mutableListOf<() -> Unit>()

inline fun pas(nume: String, acum: () -> Unit, noinline laFinal: () -> Unit) {
    println("Pas: $nume")
    acum()
    deRulatLaFinal.add(laFinal)
}

fun main() {
    pas("descarc fișierul", { println("  descărcat") }, { println("Șterg fișierul temporar") })
    pas("trimit email", { println("  trimis") }, { println("Închid conexiunea") })
    println("Curățenie:")
    deRulatLaFinal.forEach { it() }
}

Rezultat

Pas: descarc fișierul
  descărcat
Pas: trimit email
  trimis
Curățenie:
Șterg fișierul temporar
Închid conexiunea

Exemplul 3: crossinline: lambda apelată din alt obiect

bloc este apelat din interiorul unui obiect Runnable, deci trebuie marcat crossinline. În el poți folosi doar return@..., nu return simplu.

inline fun sarcina(nume: String, crossinline bloc: () -> String): Runnable =
    Runnable { println("[$nume] ${bloc()}") }

fun main() {
    val oras = "Brașov"
    val sarcini = listOf(
        sarcina("vreme") { "În $oras sunt 18 grade" },
        sarcina("trafic") { "Drum liber spre $oras" },
    )
    for (s in sarcini) s.run()
}

Rezultat

[vreme] În Brașov sunt 18 grade
[trafic] Drum liber spre Brașov

Greșeli frecvente

  • Marcarea cu inline a unei funcții fără parametri lambda: nu câștigi nimic, iar compilatorul avertizează (expected performance impact from inlining is insignificant; cu -Werror build-ul se oprește).
  • Funcții inline foarte mari, apelate în multe locuri: corpul se copiază la fiecare apel și programul crește.
  • Folosirea unui parametru inline ca valoare (lista.add(actiune)): eroare illegal usage of inline parameter, iar compilatorul îți sugerează: Add 'noinline' modifier to the parameter declaration.

Comenzi înrudite

  • lambda: O lambda este o funcție fără nume, scrisă între acolade, pe care o poți pune într-o variabilă sau trimite altei funcții.
  • reified: reified păstrează tipul generic T la rulare într-o funcție inline, ca să poți scrie x is T sau T::class.
  • Funcții de ordin superior: O funcție de ordin superior primește o funcție ca parametru sau returnează o funcție.
  • return: Încheie funcția curentă și întoarce o valoare apelantului; cu etichetă (return@forEach) iese doar din lambda.
  • value class: value class învelește o singură valoare într-un tip nou, pentru siguranță la compilare, fără costul unui obiect la rulare.