Por que break e continue não funcionam no forEach do Kotlin (e como corrigir isso)
\n\nSe você já programou em Kotlin e tentou sair prematuramente de um loop forEach usando break, provavelmente se deparou com um sublinhado vermelho na IDE e um erro de compilação. Familiar? Você não está sozinho — esta é uma das perguntas mais populares no Stack Overflow sobre Kotlin, com milhares de visualizações. Neste artigo, não vamos apenas mostrar como contornar a limitação, mas também vamos olhar por baixo dos panos: por que foi feito assim, como o return funciona dentro de uma lambda e quais alternativas existem. Vamos lá!
Exemplo clássico: erro de iniciante
\n\nImagine que você tem uma lista de números e precisa exibir apenas os três primeiros elementos. Intuitivamente, você gostaria de escrever assim:
\n\nval numbers = listOf(1, 2, 3, 4, 5)\nnumbers.forEach {\n println(it)\n if (it == 3) break // Erro de compilação!\n}\n\n\nO compilador exibirá um erro: ‘break’ and ‘continue’ are only allowed inside a loop. Por quê? Afinal, forEach não é um loop? A resposta está em como o forEach é realmente estruturado.
Anatomia do forEach: não é um loop, é uma chamada de função
\n\nVamos abrir a biblioteca padrão do Kotlin e observar a assinatura do forEach:
public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {\n for (element in this) action(element)\n}\n\n\nA palavra-chave aqui é inline. Isso significa que, em tempo de compilação, o corpo da função é inserido diretamente no local da chamada. Mas ainda assim, forEach é uma função de ordem superior que recebe outra função (lambda) como argumento. E uma lambda não é um bloco de código de loop, é um objeto-função comum. break e continue só fazem sentido dentro de construções for, while ou do-while, não dentro de uma função arbitrária.
Em outras palavras, quando você escreve numbers.forEach { ... }, você não está criando um loop — você está chamando um método que internamente usa um loop for. O compilador não pode "passar" o break da sua lambda para o loop externo, porque a lambda é uma entidade separada. Isso não é um bug, é uma decisão de design.
Como o return funciona no forEach (uma reviravolta inesperada)
\n\nJá o return dentro do forEach funciona... mas não como você espera. Veja:
fun main() {\n val numbers = listOf(1, 2, 3, 4, 5)\n numbers.forEach {\n if (it == 3) return // Sai do main()!\n println(it)\n }\n println("Esta linha não será executada")\n}\n\n\nA saída será: 1 2. O return aqui é um non-local return (retorno não local). Ele encerra não apenas a lambda, mas toda a função externa (main). Isso funciona porque forEach é declarado como inline. O compilador insere o código da lambda diretamente no corpo do main, e o return passa a se referir à fun declarada mais próxima, ou seja, o main.
Se você quiser sair apenas do forEach, e não de toda a função, use um rótulo (label):
fun main() {\n val numbers = listOf(1, 2, 3, 4, 5)\n numbers.forEach loop@{\n if (it == 3) return@loop\n println(it)\n }\n println("Esta linha será executada")\n}\n\n\nSaída: 1 2 e Esta linha será executada. O rótulo loop@ diz ao compilador: "saia da lambda, mas não do main". Na prática, isso é um análogo ao continue — pulamos a iteração atual e passamos para o próximo elemento.