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:
| Caracteristica | inline | noinline | crossinline |
|---|---|---|---|
| Codigo copiado? | Sim | Nao | Sim |
| Non-local return? | Sim | Nao | Nao |
| Pode armazenar? | Nao | Sim | Nao |
| Overhead de objeto? | Nao | Sim | Nao |
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
Usar noinline quando crossinline basta: se voce so precisa impedir non-local return mas nao precisa armazenar a lambda,
crossinlinee melhor porque mantem o beneficio de performance do inline.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.
Esquecer que noinline cria objeto: cada chamada com lambda noinline aloca um objeto Function. Em hot paths, isso pode importar.
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.
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.