O que e crossinline em Kotlin?
O modificador crossinline e usado em parametros lambda de funcoes inline para indicar que a lambda nao pode usar non-local return. Ele e necessario quando a lambda inlineada sera executada em um contexto diferente do fluxo normal da funcao, como dentro de outra lambda, um objeto anonimo ou uma coroutine.
Para entender crossinline, voce primeiro precisa entender o que acontece com lambdas em funcoes inline e o conceito de non-local return.
Non-local return: o contexto
Quando uma funcao e marcada como inline, as lambdas passadas a ela podem usar return para sair da funcao chamadora (non-local return):
inline fun executar(bloco: () -> Unit) {
bloco()
}
fun main() {
executar {
println("Antes do return")
return // Non-local return: sai de main()
}
println("Isso nunca e impresso")
}
Esse return sai de main(), nao apenas da lambda. Isso e possivel porque o compilador copia o corpo da lambda diretamente no local da chamada.
O problema que crossinline resolve
O non-local return so funciona quando a lambda e executada diretamente no fluxo da funcao inline. Se a lambda for passada para outro contexto (como um Runnable ou uma coroutine), o non-local return nao faz sentido e pode causar comportamento indefinido:
// Isso NAO compila sem crossinline
inline fun executarEmThread(bloco: () -> Unit) {
val runnable = Runnable {
bloco() // A lambda e executada em outro contexto!
}
Thread(runnable).start()
}
O compilador reclama porque bloco pode conter um return que tentaria sair da funcao chamadora, mas a lambda esta sendo executada em outra thread. A solucao e crossinline:
inline fun executarEmThread(crossinline bloco: () -> Unit) {
val runnable = Runnable {
bloco() // Agora ok: crossinline impede non-local return
}
Thread(runnable).start()
}
fun main() {
executarEmThread {
println("Executando em outra thread")
// return // Erro de compilacao! crossinline impede isso
}
}
Como funciona internamente
Com crossinline, o compilador ainda faz o inline do corpo da lambda (copiando o codigo), mas impede que return sem label seja usado. Voce ainda pode usar return@executarEmThread (local return):
inline fun processar(crossinline bloco: () -> Unit) {
val wrapper = object : Runnable {
override fun run() {
bloco()
}
}
wrapper.run()
}
fun main() {
processar {
println("Processando...")
return@processar // Local return: permitido
// return // Non-local return: proibido pelo crossinline
}
println("Continua executando")
}
Exemplo pratico: builder com callback
Um caso real onde crossinline e necessario e quando voce cria builders que armazenam lambdas para execucao posterior:
class EventoBuilder {
private val acoes = mutableListOf<() -> Unit>()
fun adicionarAcao(acao: () -> Unit) {
acoes.add(acao)
}
fun executar() {
acoes.forEach { it() }
}
}
inline fun construirEvento(crossinline configuracao: EventoBuilder.() -> Unit): EventoBuilder {
val builder = EventoBuilder()
builder.configuracao()
return builder
}
fun main() {
val evento = construirEvento {
adicionarAcao { println("Acao 1") }
adicionarAcao { println("Acao 2") }
}
evento.executar()
}
Sem crossinline, se configuracao contivesse um return, ele tentaria sair da funcao chamadora de construirEvento, o que nao faz sentido quando o builder sera usado depois.
crossinline vs noinline
Ambos lidam com restricoes em lambdas de funcoes inline, mas de formas diferentes:
inline fun exemplo(
crossinline lambdaCross: () -> Unit, // Inlineada, sem non-local return
noinline lambdaNo: () -> Unit // NAO inlineada, tratada como objeto
) {
val runnable1 = Runnable { lambdaCross() } // OK
val runnable2 = Runnable { lambdaNo() } // OK
// lambdaNo pode ser armazenada em variavel
val referencia: () -> Unit = lambdaNo // OK
// val ref2: () -> Unit = lambdaCross // Erro! crossinline ainda e inlineada
}
A diferenca chave:
crossinline: a lambda ainda e inlineada (codigo copiado), mas sem non-local return. Nao pode ser armazenada como referencia.noinline: a lambda nao e inlineada, e tratada como um objeto Function normal. Pode ser armazenada, passada como parametro, etc.
Quando usar crossinline
Use crossinline quando:
- A funcao e
inlinee a lambda sera passada para outro contexto de execucao (thread, coroutine, objeto anonimo). - Voce quer manter os beneficios de performance do inline (sem alocacao de objeto Function) mas precisa impedir non-local return.
- A lambda sera executada dentro de outra lambda nao-inline ou callback.
Na pratica, o compilador vai te avisar quando crossinline e necessario. Se voce tentar usar uma lambda inline em um contexto onde non-local return seria problematico, o compilador emite um erro sugerindo adicionar crossinline.
Erros comuns
Tentar usar return sem label em lambda crossinline: o compilador impede, mas iniciantes podem nao entender o motivo do erro. Lembre-se de que
crossinlineproibe non-local return.Confundir crossinline com noinline: usar
noinlinequandocrossinlineseria suficiente desperdiça a otimizacao do inline. Se voce so precisa impedir non-local return, usecrossinline.Usar crossinline desnecessariamente: se a lambda e executada diretamente no fluxo da funcao inline,
crossinlinenao e necessario e restringe sem motivo.Nao entender quando o compilador exige crossinline: o erro do compilador pode parecer confuso. Sempre que voce vir “can’t inline … into …”, considere adicionar
crossinlineounoinline.Esquecer que crossinline nao elimina o inline: a lambda ainda e copiada no local da chamada. Apenas o non-local return e impedido.
Exemplo com coroutines
Um cenario moderno onde crossinline aparece e em funcoes que lancam coroutines:
inline fun executarAsync(
scope: CoroutineScope,
crossinline bloco: suspend () -> Unit
) {
scope.launch {
bloco() // Executada em contexto de coroutine
}
}
Sem crossinline, a lambda poderia tentar fazer non-local return da coroutine, o que nao e valido.
Termos relacionados
- inline: modificador que instrui o compilador a copiar o corpo da funcao no local da chamada.
- noinline: impede o inline de uma lambda especifica, permitindo que seja tratada como objeto.
- Non-local return: capacidade de lambdas inline de retornar da funcao chamadora.
- Lambda: expressao funcional que pode ser passada como argumento, base do mecanismo de callbacks em Kotlin.
- Higher-Order Function: funcao que recebe ou retorna outra funcao.
- Coroutine: contexto assincrono onde crossinline e frequentemente necessario.
O crossinline e um daqueles recursos que voce nao usa todos os dias, mas quando precisa, e essencial. Ele permite que funcoes inline aproveitem a otimizacao de performance sem os riscos de non-local returns em contextos onde isso seria perigoso. O compilador do Kotlin e inteligente o suficiente para te avisar quando precisa dele.