Встроенные (inline) функции

Использование функций высшего порядка влечёт за собой снижение производительности: во-первых, любая функция является объектом, а во-вторых, происходит захват контекста замыканием, то есть функции становятся доступны переменные, объявленные вне её тела. А выделения памяти (как для объекта функции, так и для её класса) и виртуальные вызовы занимают системные ресурсы.

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

lock(l) { foo() }

Вместо создания объекта функции для параметра и генерации вызова, компилятор мог бы выполнить что-то подобное этому коду:

l.lock()
try {
    foo()
} finally {
    l.unlock()
}

Чтобы заставить компилятор поступить именно так, отметьте функцию lock модификатором inline.

inline fun <T> lock(lock: Lock, body: () -> T): T { /*...*/ }

Модификатор inline влияет и на функцию, и на лямбду, переданную ей: они обе будут встроены вместо вызова.

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

noinline

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

inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) { /*...*/ }

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

Заметьте, что если inline-функция не имеет ни inline параметров, ни параметров вещественного типа, компилятор выдаст предупреждение, так как встраивание такой функции вряд ли принесёт пользу (используйте @Suppress("NOTHING_TO_INLINE") для скрытия предупреждения, если вы уверены, что встраивание необходимо).

Нелокальные return

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

fun foo() {
    ordinaryFunction {
        return // ERROR: нельзя заставить `foo` завершиться здесь
    }
}

Но если функция, в которую передана лямбда, встроена, то return также будет встроен, поэтому так делать можно:

fun foo() {
    inlined {
        return // OK: лямбда встроена
    }
}

Такие return (находящиеся внутри лямбд, но завершающие внешнюю функцию) называются нелокальными (ориг.: non-local). Такие конструкции обычно используются в циклах, которые являются inline-функциями:

fun hasZeros(ints: List<Int>): Boolean {
    ints.forEach {
        if (it == 0) return true // return из hasZeros
    }
    return false
}

Заметьте, что некоторые inline-функции могут вызывать переданные им лямбды не напрямую в теле функции, а из иного контекста, такого как локальный объект или вложенная функция. В таких случаях, нелокальное управление потоком выполнения также запрещено в лямбдах. Чтобы указать это, параметр лямбды необходимо отметить модификатором crossinline.

inline fun f(crossinline body: () -> Unit) {
    val f = object: Runnable {
        override fun run() = body()
    }
    // ...
}

break и continue пока что недоступны во встроенных лямбдах, но мы планируем добавить их поддержку.

Параметры вещественного типа

Иногда вам необходимо получить доступ к типу, переданному в качестве параметра:

fun <T> TreeNode.findParentOfType(clazz: Class<T>): T? {
    var p = parent
    while (p != null && !clazz.isInstance(p)) {
        p = p.parent
    }
    @Suppress("UNCHECKED_CAST")
    return p as T?
}

В этом примере осуществляется проход по дереву и используется рефлексия, чтобы проверить узел на принадлежность к определённому типу. Это прекрасно работает, но вызов выглядит не очень симпатично:

treeNode.findParentOfType(MyTreeNode::class.java)

Что мы на самом деле хотим, так это передать этой функции тип, то есть вызвать её вот так:

treeNode.findParentOfType<MyTreeNode>()

В таких случаях inline-функции могут принимать параметры вещественного типа (ориг.: reified type parameters). Чтобы включить эту возможность, вы можете написать что-то вроде этого:

inline fun <reified T> TreeNode.findParentOfType(): T? {
    var p = parent
    while (p != null && p !is T) {
        p = p.parent
    }
    return p as T?
}

В коде выше тип параметра определяется с помощью модификатора reified, но он доступен внутри функции почти так же, как и обычный класс. Так как функция встроена, то для работы таких операторов как !is и as рефлексия не нужна. Также, вы можете вызывать её таким же образом, как было упомянуто выше: myTree.findParentOfType<MyTreeNodeType>().

Хотя рефлексия может быть не нужна во многих случаях, вы всё ещё можете использовать её с параметром вещественного типа.

inline fun <reified T> membersOf() = T::class.members

fun main(s: Array<String>) {
    println(membersOf<StringBuilder>().joinToString("\n"))
}

Обычная функция (не отмеченная как встроенная) не может иметь параметры вещественного типа. Тип, который не имеет представление во времени исполнения (например, параметр невещественного или фиктивного типа вроде Nothing), не может использоваться в качестве аргумента для параметра вещественного типа.

Встроенные свойства

Модификатор inline можно применять к методам доступа свойств, у которых нет теневых полей. Вы можете аннотировать отдельные методы доступа.

val foo: Foo
    inline get() = Foo()

var bar: Bar
    get() = ...
    inline set(v) { ... }

Также можно аннотировать свойство. В этом случае оба его метода доступа будут отмечены как inline.

inline var bar: Bar
    get() = ...
    set(v) { ... }

В месте вызова встроенные методы доступа встраиваются как обычные inline-функции.

Ограничения для встроенных функций в public API

Если у встроенной функции модификатор доступа public или protected, при этом она не является частью объявления с модификаторами доступа private или internal, то она считается public API модуля. Её можно вызывать в других модулях и где она встраивается в месте вызова.

Это может привести к появлению двоичной несовместимости, если модуль, который объявляет встроенную функцию, был изменён, но не перекомпилировался после внесения этих изменений.

Чтобы исключить риск двоичной несовместимости, вызванной изменением non-public API модуля, public API inline-функциям не разрешается использовать объявления non-public-API, т.е. private и internal.

Объявление с модификатором internal может быть аннотировано при помощи @PublishedApi, что позволит его использовать в public API inline-функциях. Когда встроенная функция с модификатором доступа internal помечена как @PublishedApi, её тело тоже проверяется, как если бы она была public.