Как работает inline и зачем он нужен?

В Kotlin ключевое слово inline используется для оптимизации функций, особенно тех, которые принимают лямбда-выражения в качестве аргументов. Оно сообщает компилятору, что тело функции должно быть вставлено напрямую в места вызова, а не вызываться как обычная функция. Это особенно важно при работе с функциями высшего порядка (функциями, принимающими другие функции как параметры), поскольку лямбды по умолчанию создают анонимные объекты, что может влиять на производительность.

Основная идея inline

Когда функция помечена inline, компилятор вставляет её тело прямо туда, где она вызывается. Это уменьшает накладные расходы, связанные с вызовами функций и созданием объектов лямбд. Особенно полезно в Android-разработке, где чувствительность к производительности высокая.

Пример без inline

fun doSomething(action: () -> Unit) {
action()
}
fun main() {
doSomething {
println("Hello")
}
}

Этот код создаёт объект лямбды (Function0<Unit>) в рантайме, что требует памяти и времени на вызов.

Пример с inline

inline fun doSomething(action: () -> Unit) {
action()
}

После компиляции inline-функция встраивается:

fun main() {
println("Hello")
}

Никакой лямбды не создаётся, и нет вызова doSomething.

Когда inline особенно полезен

  1. Функции с лямбдами
    При частом использовании лямбд в горячих участках кода, особенно в циклах, inline позволяет избежать создания объектов и лишних вызовов.

  2. Безопасный return из лямбд
    inline позволяет использовать return из переданных лямбд так, как будто это return из внешней функции.

Пример: return из лямбды

inline fun repeatAction(times: Int, action: () -> Unit) {
for (i in 1..times) {
action()
}
}
fun main() {
repeatAction(5) {
if (itShouldStop()) return // работает как return из main
println("Doing work")
}
}
fun itShouldStop(): Boolean = true

Если бы repeatAction не была inline, return был бы невозможен или означал бы return только из лямбды.

Несколько лямбд в параметрах и noinline

Если у inline-функции есть несколько параметров-лямбд, и вы хотите не все встраивать — используйте noinline.

inline fun process(
inlineAction: () -> Unit,
noinline logAction: () -> Unit
) {
inlineAction()
logAction() // не будет встроено
}

noinline отключает вставку кода для конкретной лямбды — это нужно, если вы хотите сохранить лямбду в переменную или передать её куда-то ещё (например, в другой поток или класс).

Использование crossinline

Если внутри лямбды в inline-функции нельзя использовать return, то нужно указать crossinline. Это предотвращает использование return, делая поведение предсказуемым.

inline fun wrap(action: () -> Unit, crossinline callback: () -> Unit) {
val runnable = Runnable {
callback() // нельзя return отсюда
}
runnable.run()
}

Ограничения inline-функций

  1. Нельзя сделать inline из рекурсивной функции — потому что тогда она станет бесконечно вставляться.

  2. Размер итогового байткода увеличивается — т.к. тело функции копируется в каждый вызов. Это может увеличить размер APK.

  3. При чрезмерном использовании может ухудшить читаемость и дебаг.

Inline-свойства

Kotlin не поддерживает inline для обычных свойств, но можно использовать val с inline get():

val greeting: String
inline get() = "Hello"

Используется редко, но можно оптимизировать простые вычисления.

Inline-классы (value classes)

Kotlin также поддерживает @JvmInline value class, которые не создают объектов в рантайме (в отличие от обычных классов). Это отдельная концепция, но перекликается с идеей inline-оптимизации:

@JvmInline
value class UserId(val id: String)

Вызовы с таким классом подставляют String напрямую и не создают объект UserId.

Inline-функции и inlining Java-функций

Java не имеет inline-механизма в языке. При вызове Java-функций из Kotlin inline не применяется. И наоборот: inline-функции Kotlin после компиляции исчезают — и в Java-коде вы не увидите вызова, а только вставленный код.

Примеры из стандартной библиотеки

Многие функции из Kotlin-stdlib являются inline, например:

  • let
  • run
  • apply
  • with
  • also
  • takeIf
  • filter, map, forEach и т.п.

Именно благодаря inline они не создают кучу лишних объектов и вызываются эффективно.

Резюме по ключевым ключевым словам

Ключевое слово Назначение
inline Встраивает тело функции, устраняет накладные расходы на вызов и создание лямбд
--- ---
noinline Запрещает встраивание конкретного параметра-лямбды
--- ---
crossinline Запрещает использование return внутри лямбды
--- ---

inline в Kotlin — мощный инструмент, когда нужно производительно и безопасно работать с лямбдами, минимизируя накладные расходы, позволяя return, и при этом повышая гибкость API.