O que e noinline em Kotlin?

O modificador noinline e usado em parametros lambda de funcoes inline para indicar que aquela lambda especifica nao deve ser inlineada. Quando uma funcao e marcada como inline, por padrao todas as lambdas passadas a ela sao copiadas no local da chamada. O noinline desativa esse comportamento para um parametro especifico, tratando a lambda como um objeto Function normal.

Isso e necessario quando voce precisa armazenar a lambda em uma variavel, passa-la para outra funcao nao-inline ou usa-la como referencia.

Por que noinline existe?

Quando uma lambda e inlineada, ela nao existe como objeto em tempo de execucao – seu codigo e copiado diretamente. Isso significa que voce nao pode:

  • Armazena-la em uma variavel ou propriedade.
  • Passa-la como argumento para funcoes nao-inline.
  • Obter uma referencia a ela.
// ERRO: nao compila
inline fun problema(acao: () -> Unit) {
    val referencia = acao  // Erro! Lambda inline nao pode ser armazenada
    executarDepois(acao)   // Erro! Nao pode passar para funcao nao-inline
}

fun executarDepois(acao: () -> Unit) {
    // ...
}

A solucao e marcar a lambda como noinline:

inline fun funciona(noinline acao: () -> Unit) {
    val referencia = acao     // OK
    executarDepois(referencia) // OK
}

Sintaxe e uso

inline fun processar(
    inlineada: () -> Unit,          // Sera inlineada normalmente
    noinline armazenavel: () -> Unit // Nao sera inlineada
) {
    inlineada()  // Codigo copiado no local da chamada

    // armazenavel pode ser tratada como objeto
    val lista = listOf(armazenavel)
    lista.forEach { it() }
}

Exemplo pratico: sistema de callbacks registrados

class GerenciadorDeEventos {
    private val callbacks = mutableListOf<() -> Unit>()

    // inline para a lambda de configuracao, noinline para o callback
    inline fun registrar(
        configurar: () -> Unit,
        noinline callback: () -> Unit
    ) {
        configurar()  // Inlineada: sem overhead de objeto
        callbacks.add(callback)  // Noinline: pode ser armazenada
    }

    fun dispararTodos() {
        callbacks.forEach { it() }
    }
}

fun main() {
    val gerenciador = GerenciadorDeEventos()

    gerenciador.registrar(
        configurar = { println("Configurando evento...") },
        callback = { println("Evento disparado!") }
    )

    gerenciador.dispararTodos()
}

Nesse exemplo, a lambda configurar e executada imediatamente e se beneficia do inline (sem alocacao de objeto). Ja o callback precisa ser armazenado para execucao posterior, entao deve ser noinline.

noinline vs crossinline

Ambos modificam o comportamento de lambdas em funcoes inline, mas de maneiras diferentes:

inline fun comparacao(
    noinline lambdaNo: () -> Unit,       // NAO inlineada, vira objeto Function
    crossinline lambdaCross: () -> Unit  // Inlineada, mas sem non-local return
) {
    // noinline: pode ser armazenada como referencia
    val ref: () -> Unit = lambdaNo  // OK
    // val ref2: () -> Unit = lambdaCross  // ERRO! crossinline ainda e inline

    // crossinline: pode ser usada em outro contexto
    val runnable = Runnable { lambdaCross() }  // OK
    // noinline tambem pode, mas sem beneficio de inline

    // noinline: non-local return NAO e possivel (e um objeto normal)
    // crossinline: non-local return NAO e possivel (proibido pelo crossinline)
    // inline normal: non-local return E possivel
}

Resumo:

Caracteristicainlinenoinlinecrossinline
Codigo copiado?SimNaoSim
Non-local return?SimNaoNao
Pode armazenar?NaoSimNao
Overhead de objeto?NaoSimNao

Quando noinline faz mais sentido que crossinline

// Use noinline quando precisa ARMAZENAR a lambda
class Cache<T> {
    private var provider: (() -> T)? = null

    inline fun configurar(noinline provider: () -> T) {
        this.provider = provider  // Precisa armazenar
    }

    fun obter(): T = provider?.invoke()
        ?: throw IllegalStateException("Provider nao configurado")
}

// Use crossinline quando precisa EXECUTAR em outro contexto sem armazenar
inline fun executarEmBackground(crossinline bloco: () -> Unit) {
    Thread { bloco() }.start()  // Executa, nao armazena
}

Impacto na performance

A diferenca de performance entre lambdas inline e noinline e a alocacao de objetos:

inline fun comNoinline(noinline acao: () -> Unit) {
    acao()  // Chama como objeto Function: cria instancia no heap
}

inline fun semNoinline(acao: () -> Unit) {
    acao()  // Codigo copiado diretamente: sem alocacao
}

fun main() {
    // Com noinline: alocacao de objeto Function a cada chamada
    repeat(1_000_000) {
        comNoinline { /* trabalho */ }
    }

    // Sem noinline: nenhuma alocacao extra
    repeat(1_000_000) {
        semNoinline { /* trabalho */ }
    }
}

Em loops apertados com milhoes de iteracoes, a diferenca pode ser significativa. Porem, para a maioria do codigo de aplicacao, o impacto e negligenciavel.

Multiplas lambdas: inline seletivo

Uma funcao inline pode ter algumas lambdas inline e outras noinline:

inline fun <T> buscarComFallback(
    buscar: () -> T?,                    // Inlineada: executada diretamente
    noinline fallback: () -> T,          // Noinline: pode ser armazenada
    noinline onErro: (Exception) -> Unit // Noinline: pode ser passada adiante
) : T {
    return try {
        buscar() ?: fallback()
    } catch (e: Exception) {
        onErro(e)
        fallback()
    }
}

Quando usar noinline

  • Armazenar lambdas: quando a lambda precisa ser salva em uma propriedade, lista ou mapa para uso posterior.
  • Passar para funcoes nao-inline: quando a lambda sera argumento de outra funcao que nao e inline.
  • Retornar lambdas: quando uma funcao inline precisa retornar uma das lambdas recebidas.
  • Colecoes de callbacks: quando voce gerencia uma lista de callbacks que serao executados depois.

Erros comuns

  1. Usar noinline quando crossinline basta: se voce so precisa impedir non-local return mas nao precisa armazenar a lambda, crossinline e melhor porque mantem o beneficio de performance do inline.

  2. Marcar todas as lambdas como noinline: se todas as lambdas sao noinline, nao ha motivo para a funcao ser inline. O compilador emite um warning nesses casos.

  3. Esquecer que noinline cria objeto: cada chamada com lambda noinline aloca um objeto Function. Em hot paths, isso pode importar.

  4. Confundir com o comportamento de funcoes normais: uma lambda noinline dentro de uma funcao inline se comporta exatamente como uma lambda em uma funcao normal – sem non-local return e com alocacao de objeto.

  5. Nao considerar reified: se a unica razao para a funcao ser inline e usar reified, e todas as lambdas precisam ser noinline, reconsidere o design.

Termos relacionados

  • inline: modificador que copia o corpo da funcao e suas lambdas no local da chamada.
  • crossinline: impede non-local return mas mantem o inline da lambda.
  • Lambda: expressao funcional que pode ser passada como argumento.
  • Higher-Order Function: funcao que recebe ou retorna lambdas, onde inline/noinline se aplicam.
  • Reified: tipo generico materializado, disponivel apenas em funcoes inline.
  • Function object: objeto criado para representar uma lambda quando ela nao e inlineada.

O noinline e uma ferramenta de precisao que permite controlar exatamente quais lambdas sao inlineadas em uma funcao inline. Usado corretamente, ele da flexibilidade para armazenar e passar lambdas quando necessario, enquanto as outras lambdas da mesma funcao continuam aproveitando os beneficios de performance do inline.