← Назад к вопросам

Для чего нужно ключевое слово crossinline в inline функции?

2.0 Middle🔥 151 комментариев
#Kotlin основы

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Назначение crossinline в Kotlin

crossinline — это модификатор для параметров inline функций, который позволяет передавать лямбду в другие функции (обычно в nested функции или корутины), не позволяя ей использовать return для выхода из внешней функции.

Основная проблема

В обычной inline функции return выходит из ВНЕШНЕЙ функции (non-local return):

inline fun forEach(action: (Int) -> Unit) {
    for (i in 1..3) {
        action(i)
    }
}

fun test() {
    forEach { i ->
        if (i == 2) return // Выходит из test(), не из forEach!
    }
    println("Это не выпечатается")
}

Что делает crossinline?

crossinline запрещает использование return в лямбде, позволяя её передавать в nested функции:

inline fun runAsync(
    crossinline block: suspend () -> Unit
) {
    GlobalScope.launch {
        block() // Передаём в suspend функцию
    }
}

fun test() {
    runAsync {
        // ❌ return здесь запрещён (нарушает crossinline)
        delay(1000)
        println("Done")
    }
}

Зачем нужен crossinline?

1. Использование лямбды в suspend функциях

Suspend функции не могут содержать non-local return:

inline fun loadDataAsync(
    crossinline onSuccess: (String) -> Unit,
    crossinline onError: (Exception) -> Unit
) {
    GlobalScope.launch {
        try {
            val data = fetchData() // suspend
            onSuccess(data)
        } catch (e: Exception) {
            onError(e)
        }
    }
}

fun test() {
    loadDataAsync(
        onSuccess = { data -> println(data) },
        onError = { e -> println(e.message) }
    )
}

2. Передача лямбды в другие функции

inline fun withLock(
    crossinline block: () -> Unit
) {
    val lock = ReentrantLock()
    lock.withLock {
        block() // Передаём в withLock
    }
}

fun test() {
    withLock {
        println("Protected code")
        // ❌ return не допускается
    }
}

3. Использование в Thread

inline fun runInThread(
    crossinline action: () -> Unit
) {
    Thread {
        action() // Передаём в конструктор Thread
    }.start()
}

fun test() {
    runInThread {
        Thread.sleep(1000)
        println("In thread")
        // ❌ return запрещён
    }
}

Inline vs Noinline vs Crossinline

inline fun example(
    block1: (Int) -> Unit,              // inline: может быть встроена
    noinline block2: (Int) -> Unit,    // noinline: может быть сохранена
    crossinline block3: (Int) -> Unit  // crossinline: может быть передана дальше
) {
    block1(1)        // Встраивается, return работает
    block2(2)        // Сохраняется, return работает
    
    launch {         // Suspend функция
        block3(3)    // Передаётся внутрь, return запрещён
    }
}

Практические примеры

1. Работа с Room DAO (асинхронный результат)

inline fun <T> withDatabase(
    database: AppDatabase,
    crossinline block: suspend (AppDatabase) -> T
) {
    viewModelScope.launch {
        try {
            val result = block(database)
            // Обработка результата
        } catch (e: Exception) {
            // Обработка ошибки
        }
    }
}

// Использование
withDatabase(database) { db ->
    db.userDao().getUsers()
}

2. Retry с задержкой

inline fun retryWithDelay(
    maxAttempts: Int = 3,
    delay: Long = 1000,
    crossinline block: suspend () -> Unit
) {
    viewModelScope.launch {
        repeat(maxAttempts) { attempt ->
            try {
                block()
                return@launch // Успех
            } catch (e: Exception) {
                if (attempt == maxAttempts - 1) throw e
                delay(delay)
            }
        }
    }
}

// Использование
retryWithDelay {
    api.fetchData()
}

3. Обработка Event с корутинами

inline fun observeEvent(
    flow: Flow<Event>,
    crossinline handler: suspend (Event) -> Unit
) {
    viewModelScope.launch {
        flow.collect { event ->
            handler(event)
        }
    }
}

fun test() {
    observeEvent(eventFlow) { event ->
        println(event)
        // ❌ return запрещён, используй return@observeEvent
    }
}

Почему нельзя просто использовать return?

inline fun problem(
    block: () -> Unit  // БЕЗ crossinline
) {
    Thread {
        block() // ❌ ОШИБКА: return будет выходить из problem(), 
                // но мы уже в другом потоке!
    }.start()
}

Таблица модификаторов

МодификаторВстраиваетсяСохраняетсяПередаётсяreturn работает
Обычный✅ Да❌ Нет❌ Нет✅ Да (non-local)
noinline❌ Нет✅ Да✅ Да✅ Да
crossinline✅ Да❌ Нет✅ Да❌ Нет

Итог

crossinline нужен когда:

  • Лямбда передаётся в suspend функцию
  • Лямбда передаётся в Thread или другой контекст выполнения
  • Нужно встраить лямбду, но запретить non-local return
  • Используется с GlobalScope.launch, viewModelScope.launch и т.д.

Правило:

  • inline = встраивание + non-local return
  • noinline = сохранение + non-local return
  • crossinline = встраивание + БЕЗ non-local return
Для чего нужно ключевое слово crossinline в inline функции? | PrepBro