expect și actual în Kotlin

Corutine, adnotări și multiplatform Kotlin 1.2 (multiplatform; clasele expect/actual sunt încă Beta)

expect declară în codul comun ceva ce fiecare platformă (JVM, Android, iOS, JS) implementează separat cu actual.

Sintaxă
// commonMain
expect fun numePlatforma(): String

// jvmMain
actual fun numePlatforma(): String = "JVM"

// iosMain
actual fun numePlatforma(): String = "iOS"

Ce face expect și actual

În proiectele Kotlin Multiplatform, cea mai mare parte a codului stă într-un modul comun (commonMain), care se compilează pentru mai multe platforme: JVM/Android, iOS, web, desktop. Unele lucruri diferă însă de la o platformă la alta: ora sistemului, generatorul de UUID, stocarea fișierelor. Pentru ele scrii în codul comun o declarație expect (fără implementare), iar în fiecare modul de platformă (jvmMain, iosMain, jsMain) o implementare actual cu exact aceeași semnătură.

Pot fi expect: funcții, proprietăți, clase, obiecte, interfețe, enum-uri și adnotări. Compilatorul verifică faptul că fiecare platformă are un actual potrivit. Recomandarea oficială este să folosești funcții și interfețe expect (sau o interfață obișnuită cu implementări injectate) și să eviți clasele expect, care sunt încă Beta.

Mecanismul are nevoie de un proiect Gradle multiplatform; un singur fișier compilat cu kotlinc pentru JVM nu poate avea expect fără actual. De aceea exemplele de mai jos arată structura proiectului și rezultatul de pe fiecare platformă, fără să fie rulate aici.

Exemple

Exemplul 1: O funcție cu implementare pe fiecare platformă

Codul comun folosește platforma() fără să știe unde rulează. Rezultatul depinde de ținta pe care o rulezi.

// src/commonMain/kotlin/Salut.kt
expect fun platforma(): String

fun salut() = "Salut de pe ${platforma()}!"

// src/jvmMain/kotlin/Platforma.jvm.kt
actual fun platforma(): String = "JVM " + System.getProperty("java.version")

// src/iosMain/kotlin/Platforma.ios.kt
import platform.UIKit.UIDevice
actual fun platforma(): String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion

Rezultat (pe JVM, respectiv pe un iPhone)

Salut de pe JVM 27!
Salut de pe iOS 26.0!

Exemplul 2: Alternativa recomandată: interfață comună

În loc de expect class, codul comun definește o interfață, iar fiecare platformă dă o implementare. Partea comună de mai jos rulează pe JVM ca un program obișnuit.

interface Ceas {
    fun oraCurenta(): String
}

class CeasFix(private val ora: String) : Ceas {
    override fun oraCurenta() = ora
}

class Programare(private val ceas: Ceas) {
    fun mesaj(pacient: String) = "Pacientul $pacient a fost înregistrat la ${ceas.oraCurenta()}"
}

fun main() {
    val p = Programare(CeasFix("09:30"))
    println(p.mesaj("Ana Pop"))
}

Rezultat

Pacientul Ana Pop a fost înregistrat la 09:30

Greșeli frecvente

  • Un expect fără actual pe una dintre platforme: compilarea pentru acea platformă se oprește cu o eroare care spune că declarația expect nu are actual în modulul respectiv.
  • Semnături diferite între expect și actual (alt tip de parametru, alt tip returnat): compilatorul le tratează ca declarații diferite și dă eroare.
  • Folosirea lui expect pentru orice diferență mică: de multe ori o interfață și injectarea implementării sunt mai simple și mai ușor de testat.

Comenzi înrudite

  • external: external declară o funcție implementată în afara lui Kotlin: în cod nativ prin JNI pe JVM, sau în JavaScript pentru Kotlin/JS.
  • interface: interface descrie ce poate face un obiect (metode și proprietăți), fără stare; o clasă poate implementa mai multe interfețe.
  • kotlinc: Compilatorul Kotlin din linia de comandă: transformă fișierele .kt într-un program care rulează pe JVM.
  • annotation class și ținte de adnotare: annotation class declară o adnotare (@Nume) care adaugă informații la cod; @field:, @get:, @file: aleg exact unde se pune.