Почему нельзя сделать все функции inline

Ответ

Inline-функции в Kotlin разворачиваются во время компиляции, что устраняет накладные расходы на вызов функции. Однако есть ограничения:

  1. Размер кода: Каждый вызов inline-функции заменяется её телом, что увеличивает размер билда. Если функция большая или вызывается часто, это может негативно сказаться на производительности.

  2. Ограничения: В inline-функциях нельзя использовать нелокальные return (кроме лямбд с crossinline), и они не могут быть рекурсивными.

  3. Лямбды: Inline эффективен для функций с лямбда-параметрами, так как избегает создания анонимных классов. Но если лямбда передаётся в другую не-inline функцию, преимущество теряется.

Пример:

inline fun measureTime(block: () -> Unit) {
    val start = System.currentTimeMillis()
    block()
    println("Time: ${System.currentTimeMillis() - start}ms")
}

Ответ 18+ 🔞

Слушай, давай разберёмся с этими inline-функциями в Kotlin, а то народ часто нихуя не понимает, где их применять, а где нет. Это как с машиной — если ты будешь на каждый чих вызывать эвакуатор, то денег на бензин не останется, хотя сама идея вроде и хорошая.

Короче, суть в чём: компилятор, вместо того чтобы вызывать функцию, тупо вставляет её код прямо на место вызова. Представь, что ты вместо того чтобы крикнуть «Вася, принеси пива!», каждый раз сам встаёшь и идешь на кухню. Экономишь время на крике, но ноги болят. Это и есть инлайнинг, ёпта.

Но есть подводные камни, о которых надо помнить, а то будет вам хиросима и нигерсраки.

Во-первых, размер кода. Если функция у тебя большая, как манда с ушами, и ты её вызываешь в тысяче мест, то компилятор эту простыню текста скопипастит везде. В итоге размер твоего APK или JAR-ника раздуется, как шар перед грозой. Это называется «раздувание билда», и оно тебе нахуй не сдалось, особенно на мобилках, где каждый килобайт на счету. Поэтому инлайнить имеет смысл только небольшие функции, где выгода от избегания вызова перевешивает рост размера.

Во-вторых, ограничения. Тут как с полупидором — вроде и мужик, но не совсем. В inline-функциях нельзя делать нелокальные return из лямбд, которые туда передал, если только ты специально не пометишь параметр как crossinline. И уж точно они не могут быть рекурсивными — представь, что компилятор пытается развернуть бесконечную рекурсию. Он просто охуеет и сдаст тебе ошибку, и будет прав, пидарас шерстяной.

В-третьих, лямбды. Вот где инлайнинг реально блестит, как хуй в пальто! Обычно, когда ты передаешь лямбду в функцию, под капотом создается анонимный класс — это дополнительные расходы. А если функция inline, то лямбда тоже инлайнится, и никаких лишних объектов не создаётся. Красота! Но есть нюанс: если ты эту лямбду потом передашь куда-то ещё, в какую-то обычную (не-inline) функцию, то вся магия накрывается медным тазом. Компилятору уже не развернуть её, и он создаст тот самый объект. Так что доверия ебать ноль — всегда смотри, куда твоя лямбда идёт дальше.

Вот, смотри на пример, чтобы всё встало на свои места:

inline fun measureTime(block: () -> Unit) {
    val start = System.currentTimeMillis()
    block()
    println("Time: ${System.currentTimeMillis() - start}ms")
}

Когда ты вызовешь measureTime { какой-то код }, компилятор не станет вызывать функцию. Он сделает вот так:

val start = System.currentTimeMillis()
какой-то код // Тело лямбды вставлено прямо сюда
println("Time: ${System.currentTimeMillis() - start}ms")

Видишь? Нет вызова measureTime, нет лишнего объекта для лямбды. Чистая магия и производительность. Но если block() внутри этой функции будет передан в какую-нибудь другую, не-inline функцию, то пиши пропало — магия исчезнет.

Короче, инструмент мощный, но как скальпель — режет хорошо, но если ты им гвозди забивать начнёшь, то сам от себя охуеешь от результата. Используй с умом, э бошка думай.