break e continue no forEach do Kotlin: como funciona por baixo dos panos

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Por que break e continue não funcionam no forEach do Kotlin (e como corrigir isso)

\n\n

Se 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á!

\n\n

Exemplo clássico: erro de iniciante

\n\n

Imagine que você tem uma lista de números e precisa exibir apenas os três primeiros elementos. Intuitivamente, você gostaria de escrever assim:

\n\n
val numbers = listOf(1, 2, 3, 4, 5)\nnumbers.forEach {\n    println(it)\n    if (it == 3) break // Erro de compilação!\n}\n
\n\n

O 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.

\n\n

Anatomia do forEach: não é um loop, é uma chamada de função

\n\n

Vamos abrir a biblioteca padrão do Kotlin e observar a assinatura do forEach:

\n\n
public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {\n    for (element in this) action(element)\n}\n
\n\n

A 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.

\n\n

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.

\n\n

Como o return funciona no forEach (uma reviravolta inesperada)

\n\n

Já o return dentro do forEach funciona... mas não como você espera. Veja:

\n\n
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\n

A 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.

\n\n

Se você quiser sair apenas do forEach, e não de toda a função, use um rótulo (label):

\n\n
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\n

Saí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.

\n\n

Como emular break e continue no forEach<

Blogs

Book Recommendations