O que é inline em Kotlin?

O modificador inline em Kotlin instrui o compilador a copiar o corpo da função diretamente no local da chamada, em vez de criar uma chamada de função normal. Isso elimina o overhead de criação de objetos para lambdas, melhorando a performance.

Toda vez que você passa uma lambda como argumento, o Kotlin normalmente cria um objeto anônimo. Com inline, esse objeto não é criado — o código da lambda é copiado diretamente.

Sem inline vs com inline

// Sem inline: cria um objeto Function para cada chamada
fun semInline(bloco: () -> Unit) {
    bloco()
}

// Com inline: codigo é copiado no local da chamada
inline fun comInline(bloco: () -> Unit) {
    bloco()
}

Na prática, o resultado é o mesmo. A diferença é por baixo dos panos: a versão inline não aloca um objeto Function.

Exemplo prático: medindo tempo

inline fun medirTempo(bloco: () -> Unit) {
    val inicio = System.currentTimeMillis()
    bloco()
    val fim = System.currentTimeMillis()
    println("Tempo: ${fim - inicio}ms")
}

fun main() {
    medirTempo {
        val soma = (1..1_000_000).sum()
        println("Soma: $soma")
    }
}

Non-local return

Uma vantagem do inline é permitir non-local return — retornar da função que chamou, não só da lambda:

inline fun buscarPrimeiro(lista: List<Int>, condicao: (Int) -> Boolean): Int? {
    for (item in lista) {
        if (condicao(item)) return item
    }
    return null
}

fun main() {
    val resultado = buscarPrimeiro(listOf(1, 3, 5, 8, 10)) { it % 2 == 0 }
    println(resultado) // 8
}

noinline e crossinline

Se a função tem vários parâmetros lambda e você não quer inlinear todos:

inline fun exemplo(
    inlineada: () -> Unit,
    noinline naoInlineada: () -> Unit
) {
    inlineada()
    naoInlineada()
}

E crossinline impede non-local return em lambdas que serão executadas em outro contexto:

inline fun executarEmThread(crossinline bloco: () -> Unit) {
    Thread {
        bloco() // crossinline impede "return" aqui
    }.start()
}

O crossinline é necessário quando a lambda será executada em um contexto diferente (como outra thread ou callback), onde um non-local return não faz sentido e causaria comportamento indefinido.

Quando usar?

Use inline em funções que recebem lambdas como parâmetro e são chamadas com frequência. A biblioteca padrão do Kotlin usa inline em let, apply, map, filter e praticamente todas as scope functions.

Reified type parameters

O inline desbloqueia uma funcionalidade exclusiva: os reified type parameters. Normalmente, tipos genéricos são apagados em runtime (type erasure), mas com inline e reified você pode acessá-los:

inline fun <reified T> converterPara(json: String): T {
    println("Convertendo para ${T::class.simpleName}")
    // Em um caso real, usaria uma biblioteca como Gson ou kotlinx.serialization
    throw NotImplementedError("Exemplo ilustrativo")
}

inline fun <reified T> List<*>.filtrarPorTipo(): List<T> {
    return this.filterIsInstance<T>()
}

fun main() {
    val misturado: List<Any> = listOf(1, "texto", 2.5, "outro", 3)
    val strings = misturado.filtrarPorTipo<String>()
    println(strings) // [texto, outro]

    val inteiros = misturado.filtrarPorTipo<Int>()
    println(inteiros) // [1, 3]
}

Sem inline, seria necessário passar a classe como parâmetro: fun <T> converter(json: String, tipo: Class<T>). Com reified, a API fica muito mais limpa.

Inline properties

Além de funções, Kotlin permite marcar getters e setters de propriedades como inline:

class Configuracao(private val mapa: Map<String, String>) {
    inline val versao: String
        get() = mapa["versao"] ?: "1.0.0"

    inline val ambiente: String
        get() = mapa["ambiente"] ?: "desenvolvimento"
}

Isso é útil quando a propriedade não tem backing field e o getter é simples o suficiente para ser copiado no local de uso.

Casos de Uso no Mundo Real

  • Scope functions da biblioteca padrão: let, run, with, apply e also são todas inline, o que significa que usá-las não tem custo de performance em relação a escrever o código diretamente.
  • DSLs type-safe: frameworks como Jetpack Compose e Ktor usam funções inline para criar DSLs onde o código do usuário é inlineado, mantendo a performance e permitindo non-local returns.
  • Serialização e deserialização: bibliotecas usam inline com reified para inferir o tipo de destino automaticamente, como json.decodeFromString<MeuObjeto>(texto) em kotlinx.serialization.
  • Logging condicional: funções inline de log como logger.debug { "mensagem cara: ${calcular()}" } evitam a construção da string quando o nível de log está desabilitado, já que o lambda só é avaliado se necessário.

Boas Práticas

  • Use inline apenas em funções que recebem lambdas. Marcar funções sem parâmetros de função como inline traz pouco benefício e pode aumentar o tamanho do bytecode.
  • Marque com noinline parâmetros lambda que precisam ser armazenados em variáveis, passados para outras funções não-inline ou usados como objetos.
  • Prefira crossinline quando a lambda será executada em outro contexto (threads, callbacks) para evitar bugs sutis com non-local returns.
  • Combine inline com reified para criar APIs mais limpas que não exigem passagem explícita de Class<T> ou KClass<T>.
  • Monitore o tamanho do bytecode gerado. Funções inline muito grandes chamadas em muitos lugares podem aumentar significativamente o tamanho do APK ou JAR.

Erros Comuns

  • Tornar todas as funções inline indiscriminadamente: o inline copia o corpo da função em cada local de chamada. Funções grandes inlineadas em muitos lugares inflam o bytecode sem ganho real de performance.
  • Esquecer de marcar parâmetros como noinline quando necessário: se você tenta armazenar uma lambda inline em uma variável ou passá-la para outra função, o compilador gera um erro. A solução é marcar esse parâmetro como noinline.
  • Non-local return inesperado: em funções inline, um return dentro da lambda retorna da função que chamou, não apenas da lambda. Isso pode causar comportamento inesperado se você esperava apenas sair do bloco.
  • Usar inline em funções de interface ou virtuais: funções inline não podem ser open, override ou abstratas, pois o compilador precisa do corpo da função em tempo de compilação para copiar.
  • Confundir reified com reflexão: reified funciona apenas em tempo de compilação dentro de funções inline. Você não pode usar reified em funções não-inline ou armazenar o tipo em uma variável para uso posterior.

Perguntas Frequentes

O inline melhora a performance de toda função? Não. O benefício principal é evitar a alocação de objetos Function para lambdas. Em funções que não recebem lambdas, o ganho é mínimo e o custo de aumento do bytecode pode não valer a pena.

Qual a diferença entre noinline e crossinline? noinline impede completamente o inlining da lambda, permitindo que ela seja tratada como objeto. crossinline mantém o inlining mas proíbe non-local returns, necessário quando a lambda é executada em outro contexto como uma thread.

Por que as scope functions do Kotlin são inline? Para que o uso de let, apply, run e similares tenha zero overhead. Sem inline, cada chamada criaria um objeto Function, o que seria inaceitável para funções tão frequentemente utilizadas.

Posso usar reified sem inline? Não. O modificador reified só funciona em funções inline porque depende do compilador substituir o tipo genérico pelo tipo concreto no local de chamada. Sem inlining, o tipo seria apagado por type erasure.

Termos Relacionados

  • Higher-Order Function — funções que recebem lambdas e são as principais candidatas para inline
  • Lambda — funções anônimas cujo overhead de alocação é eliminado por inline
  • Generics — tipos genéricos que podem ser preservados em runtime com reified e inline
  • Fun — palavra-chave usada para declarar funções que podem receber o modificador inline