O que é crossinline em Kotlin?
O modificador crossinline é usado em parametros lambda de funções inline para indicar que a lambda não pode usar non-local return. Ele e necessário quando a lambda inlineada sera executada em um contexto diferente do fluxo normal da função, como dentro de outra lambda, um objeto anonimo ou uma coroutine.
Para entender crossinline, você primeiro precisa entender o que acontece com lambdas em funções inline é o conceito de non-local return.
Non-local return: o contexto
Quando uma função e marcada como inline, as lambdas passadas a ela podem usar return para sair da função 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(), não apenas da lambda. Isso e possível porque o compilador copia o corpo da lambda diretamente no local da chamada.
O problema que crossinline resolve
O non-local return só funciona quando a lambda e executada diretamente no fluxo da função inline. Se a lambda for passada para outro contexto (como um Runnable ou uma coroutine), o non-local return não 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 função chamadora, mas a lambda esta sendo executada em outra thread. A solução 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 código), mas impede que return sem label seja usado. Você 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 prático: builder com callback
Um caso real onde crossinline e necessário e quando você cria builders que armazenam lambdas para execução 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 configuração contivesse um return, ele tentaria sair da função chamadora de construirEvento, o que não faz sentido quando o builder sera usado depois.
crossinline vs noinline
Ambos lidam com restricoes em lambdas de funções 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 referência: () -> Unit = lambdaNo // OK
// val ref2: () -> Unit = lambdaCross // Erro! crossinline ainda e inlineada
}
A diferenca chave:
crossinline: a lambda ainda e inlineada (código copiado), mas sem non-local return. Nao pode ser armazenada como referência.noinline: a lambda não e inlineada, e tratada como um objeto Function normal. Pode ser armazenada, passada como parametro, etc.
Quando usar crossinline
Use crossinline quando:
- A função e
inlinee a lambda sera passada para outro contexto de execução (thread, coroutine, objeto anonimo). - Você 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 não-inline ou callback.
Na prática, o compilador vai te avisar quando crossinline e necessário. Se você tentar usar uma lambda inline em um contexto onde non-local return seria problematico, o compilador emite um erro sugerindo adicionar crossinline.
Casos de Uso no Mundo Real
Frameworks de injecao de dependência: bibliotecas como Koin usam funções inline com lambdas de configuração que sao armazenadas internamente. O
crossinlinegarante que o código do usuário não tente fazer non-local return de dentro do builder de modulos, mantendo a integridade do ciclo de vida do framework.DSLs de construcao de UI: em frameworks como Jetpack Compose e bibliotecas de UI customizadas, lambdas de composicao sao passadas para contextos internos de renderizacao. O
crossinlinepermite que essas lambdas sejam inlineadas para performance, sem permitir que umreturninterrompa o pipeline de renderizacao.Wrappers de execução assíncrona: ao criar funções utilitarias que encapsulam o lancamento de coroutines ou execução em threads separadas, o
crossinlinee essencial para impedir que a lambda tente retornar da função chamadora quando esta sendo executada em outro contexto de thread.Sistemas de eventos e callbacks: em arquiteturas orientadas a eventos, lambdas sao frequentemente registradas para execução futura. funções inline que registram esses handlers precisam de
crossinlinepara manter os beneficios de performance do inline sem permitir non-local returns que não fariam sentido no momento da execução do evento.
Boas Praticas
- Prefira
crossinlineem vez denoinlinequando o único problema e o non-local return. Assim você mantém a otimização de inline (sem alocacao de objeto Function) e apenas restringe o tipo de return permitido. - Deixe o compilador guiar você: na maioria dos casos, o compilador emitira um erro claro indicando que
crossinlinee necessário. Nao adicione o modificador preventivamente sem necessidade. - Documente o motivo do
crossinlinena função com um comentario breve, especialmente em APIs publicas, para que outros desenvolvedores entendam por que a lambda não suporta non-local return. - Ao projetar APIs inline com múltiplas lambdas, avalie cada parametro individualmente. Nem todas as lambdas precisam de
crossinline; aplique apenas naquelas que serao executadas em outro contexto. - Combine
crossinlinecom contratos (contract) quando possível para preservar a capacidade do compilador de fazer smart casts mesmo com a restricao de return.
Perguntas Frequentes
P: Qual a diferenca entre crossinline e noinline na prática?
R: O crossinline mantém o inline da lambda (o código e copiado no local da chamada, sem alocacao de objeto), mas impede non-local return. Ja o noinline faz com que a lambda seja tratada como um objeto Function normal, permitindo que seja armazenada em variaveis ou passada como argumento, porem sem os beneficios de performance do inline.
P: O crossinline afeta a performance do código gerado?
R: Nao negativamente. O crossinline mantém todos os beneficios de performance do inline. A única diferenca em relacao a uma lambda inline normal e a restricao de compilação que impede non-local return. O bytecode gerado e praticamente identico.
P: Posso usar return com label dentro de uma lambda crossinline?
R: Sim. O crossinline proibe apenas o non-local return (o return sem label que sairia da função chamadora). Voce ainda pode usar return@nomeDaFuncao para fazer um local return, que encerra apenas a execução da lambda.
P: O compilador sempre avisa quando preciso usar crossinline?
R: Sim, o compilador Kotlin detecta quando uma lambda inline e passada para um contexto onde non-local return seria inválido e emite um erro de compilação. A mensagem geralmente sugere adicionar crossinline ou noinline ao parametro.
Erros comuns
Tentar usar return sem label em lambda crossinline: o compilador impede, mas iniciantes podem não entender o motivo do erro. Lembre-se de que
crossinlineproibe non-local return.Confundir crossinline com noinline: usar
noinlinequandocrossinlineseria suficiente desperdiça a otimização do inline. Se você só precisa impedir non-local return, usecrossinline.Usar crossinline desnecessariamente: se a lambda e executada diretamente no fluxo da função inline,
crossinlinenão e necessário e restringe sem motivo.Nao entender quando o compilador exige crossinline: o erro do compilador pode parecer confuso. Sempre que você vir “can’t inline … into …”, considere adicionar
crossinlineounoinline.Esquecer que crossinline não elimina o inline: a lambda ainda e copiada no local da chamada. Apenas o non-local return e impedido.
Exemplo com coroutines
Um cenário moderno onde crossinline aparece e em funções 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 não e válido.
Termos relacionados
- inline: modificador que instrui o compilador a copiar o corpo da função no local da chamada.
- noinline: impede o inline de uma lambda específica, permitindo que seja tratada como objeto.
- Non-local return: capacidade de lambdas inline de retornar da função chamadora.
- Lambda: expressao funcional que pode ser passada como argumento, base do mecanismo de callbacks em Kotlin.
- Higher-Order Function: função que recebe ou retorna outra função.
- Coroutine: contexto assíncrono onde crossinline e frequentemente necessário.
O crossinline e um daqueles recursos que você não usa todos os dias, mas quando precisa, e essencial. Ele permite que funções inline aproveitem a otimização 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.