A desestruturação é um dos recursos mais usados no Kotlin. Toda vez que você escreve val (nome, idade) = pessoa, está usando esse mecanismo. O problema é que, até agora, a desestruturação dependia exclusivamente da posição dos componentes — e isso gerava bugs sutis que só apareciam em produção.
A partir do Kotlin 2.3.20, o compilador introduziu a desestruturação por nome (name-based destructuring) como recurso experimental. A proposta KEEP-0438 define uma mudança fundamental na forma como o Kotlin resolve variáveis em declarações de desestruturação, trazendo segurança semântica sem sacrificar a ergonomia da linguagem.
Neste artigo, vamos explorar o problema que motivou essa mudança, a nova sintaxe, exemplos práticos e como preparar seu código para a migração.
O problema da desestruturação por posição
Considere esta data class simples:
data class Pessoa(val nome: String, val endereco: String)
val (cidade, nome) = Pessoa("João", "Amsterdã")
Esse código compila sem erros. Mas cidade recebe "João" e nome recebe "Amsterdã" — exatamente o oposto do que os nomes das variáveis sugerem. O compilador não se importa com os nomes porque a desestruturação atual usa funções component1(), component2(), etc., que dependem exclusivamente da ordem.
Quando o bug aparece de verdade
O cenário mais perigoso acontece quando alguém adiciona uma propriedade no meio de uma data class:
// Versão 1
data class Transacao(val valor: Double, val descricao: String)
val (valor, descricao) = transacao // OK
// Versão 2 — alguém adiciona 'tipo' entre valor e descricao
data class Transacao(val valor: Double, val tipo: String, val descricao: String)
val (valor, descricao) = transacao // QUEBROU: descricao agora recebe 'tipo'
Esse tipo de bug é especialmente perigoso em código de backend onde data classes representam DTOs de APIs REST. A mudança silenciosa de semântica não gera erro de compilação — o tipo é compatível — mas corrompe dados em runtime.
Se você já viu esse tipo de problema em projetos reais, sabe que é difícil de diagnosticar. Nosso guia sobre testes com JUnit5 e MockK ajuda a montar cenários que capturam regressões desse tipo, mas a solução definitiva é corrigir o mecanismo de linguagem.
Como funciona a desestruturação por nome
Com a flag do compilador habilitada, a desestruturação por nome resolve variáveis pelo nome da propriedade em vez da posição. A sintaxe usa parênteses com val explícito:
data class Transacao(val valor: Double, val tipo: String, val descricao: String)
// Desestruturação por nome — ordem não importa
val (val descricao, val valor) = transacao
// descricao = transacao.descricao (correto!)
// valor = transacao.valor (correto!)
O compilador faz o match entre o nome da variável e o nome da propriedade. A ordem na declaração não importa mais. Isso elimina toda a classe de bugs descrita acima.
Renomeando variáveis
Quando você precisa usar um nome diferente da propriedade, a sintaxe suporta aliases:
val (val desc = descricao, val amount = valor) = transacao
// desc recebe transacao.descricao
// amount recebe transacao.valor
Isso é útil quando o nome da propriedade não é ideal para o contexto local ou quando há conflito de nomes com variáveis existentes no escopo.
Nova sintaxe de colchetes para posição
A proposta também introduz colchetes para desestruturação posicional explícita. Isso é importante para tipos onde a ordem dos elementos é o que importa, como Pair, Triple, listas e mapas:
// Posicional com colchetes — para Pair, Triple, etc.
val [primeiro, segundo] = Pair("Kotlin", "Java")
// Em loops de Map
for ([chave, valor] in mapaDeCidades) {
println("$chave -> $valor")
}
A distinção visual entre parênteses (nome) e colchetes (posição) torna o código auto-documentado. Quem lê sabe imediatamente qual estratégia está sendo usada.
Comparação direta
data class Config(val host: String, val port: Int, val debug: Boolean)
val config = Config("localhost", 8080, true)
// Por NOME — match por propriedade (nova sintaxe)
val (val debug, val host) = config
// debug = true, host = "localhost"
// Por POSIÇÃO — match por componente (colchetes)
val [primeiro, segundo] = config
// primeiro = "localhost", segundo = 8080
Habilitando no seu projeto
Para usar a desestruturação por nome no Kotlin 2.3.20+, adicione a flag do compilador no Gradle:
// build.gradle.kts
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xname-based-destructuring=only-syntax")
}
}
A flag aceita três modos progressivos:
| Modo | Comportamento |
|---|---|
only-syntax | Habilita a sintaxe val (val x, val y) sem alterar código existente |
name-mismatch | Emite warnings quando nomes de variáveis não correspondem aos da data class |
complete | Ativa o modo completo com parênteses para nome e colchetes para posição |
Para projetos existentes, comece com only-syntax para experimentar sem risco. Depois evolua para name-mismatch para identificar desestruturações potencialmente incorretas no código atual.
Exemplos práticos em cenários reais
DTO de API com Spring Boot
data class PedidoDTO(
val id: Long,
val cliente: String,
val itens: List<ItemDTO>,
val valorTotal: Double,
val status: String
)
fun processarPedido(dto: PedidoDTO) {
// Por nome — extraímos apenas o que precisamos, na ordem que queremos
val (val status, val valorTotal, val cliente) = dto
when (status) {
"PENDENTE" -> enviarNotificacao(cliente, valorTotal)
"PAGO" -> iniciarEntrega(dto)
"CANCELADO" -> estornar(cliente, valorTotal)
}
}
A vantagem aqui é clara: se alguém adicionar uma nova propriedade ao PedidoDTO amanhã, o código de desestruturação continua funcionando corretamente. Isso é especialmente relevante em projetos de microserviços com Kotlin onde múltiplos times evoluem DTOs compartilhados.
Desestruturação em lambdas
A nova sintaxe funciona dentro de lambdas, o que é útil com collections e Flow:
data class Metrica(val nome: String, val valor: Double, val unidade: String)
val metricas = listOf(
Metrica("cpu_usage", 75.3, "%"),
Metrica("memory_mb", 2048.0, "MB"),
Metrica("requests_per_sec", 1200.0, "req/s")
)
// Desestruturação por nome na lambda
metricas
.filter { (val valor) -> valor > 100 }
.forEach { (val nome, val valor, val unidade) ->
println("Alerta: $nome = $valor$unidade")
}
Combinando com sealed classes
sealed class Resultado {
data class Sucesso(val dados: String, val timestamp: Long) : Resultado()
data class Erro(val mensagem: String, val codigo: Int) : Resultado()
}
fun tratarResultado(resultado: Resultado) {
when (resultado) {
is Resultado.Sucesso -> {
val (val dados, val timestamp) = resultado
salvarCache(dados, timestamp)
}
is Resultado.Erro -> {
val (val codigo, val mensagem) = resultado
log.error("Erro $codigo: $mensagem")
}
}
}
Com sealed classes, a desestruturação por nome torna o pattern matching mais legível. A ordem dos campos na declaração reflete a importância semântica no contexto, não a posição na data class.
Funciona com classes normais?
Sim, desde que a classe exponha propriedades públicas. A desestruturação por nome não depende de componentN():
class Configuracao(
val ambiente: String,
val versao: String,
val porta: Int
) {
val urlCompleta: String get() = "$ambiente:$porta/v$versao"
}
val (val ambiente, val porta) = Configuracao("prod", "2.0", 443)
// ambiente = "prod", porta = 443
Isso abre possibilidades para classes que antes não suportavam desestruturação sem implementar operator fun componentN() manualmente. Para quem trabalha com extension functions, isso significa que a desestruturação agora funciona com qualquer classe que tenha propriedades públicas.
Plano de migração da JetBrains
A KEEP-0438 define um plano de migração em três fases:
Fase 1 (atual): a nova sintaxe coexiste com a antiga. Parênteses sem val continuam sendo posicionais. A flag only-syntax permite usar a nova sintaxe sem efeitos colaterais.
Fase 2: o compilador emite warnings para desestruturações posicionais onde os nomes das variáveis não correspondem às propriedades. Isso ajuda a identificar código potencialmente incorreto.
Fase 3 (futuro): val (x, y) = e passa a significar desestruturação por nome por padrão. A desestruturação posicional migra para a sintaxe de colchetes [x, y].
Para projetos que usam version catalogs no Gradle, a recomendação é fixar a flag do compilador no catálogo centralizado para garantir consistência entre módulos.
Impacto na compatibilidade binária
Um benefício menos óbvio da desestruturação por nome é a estabilidade da API binária. Com a abordagem posicional, adicionar uma propriedade no meio de uma data class quebra código que faz desestruturação. Com a abordagem por nome, a adição de novos campos não afeta código existente — apenas os campos explicitamente nomeados são extraídos.
Isso é particularmente importante para autores de bibliotecas. Se você mantém uma lib Kotlin publicada no Maven Central, a desestruturação por nome permite evoluir data classes sem quebrar a compatibilidade com consumidores. Para quem trabalha com serialização avançada, essa estabilidade complementa o versionamento de schemas.
Conclusão
A desestruturação por nome resolve um problema real que existia desde o Kotlin 1.0. A dependência da posição sempre foi uma fonte de bugs silenciosos, especialmente em equipes grandes onde data classes evoluem com frequência.
A recomendação prática: habilite only-syntax no seu projeto hoje para experimentar. Depois migre para name-mismatch para encontrar desestruturações suspeitas no código existente. Quando a Fase 3 chegar em uma release futura, seu código já estará preparado.
Se você está acompanhando as novidades do Kotlin em 2026, esse recurso se encaixa na mesma filosofia de segurança de tipo que motivou null safety, value classes e explicit backing fields. O Kotlin continua priorizando código correto por construção. Em outras linguagens do ecossistema de sistemas, Rust resolve desestruturação de structs por nome desde sua primeira versão estável, e a inspiração é clara na proposta da KEEP-0438. Linguagens como Python com seu unpacking e Go com acesso explícito a campos de structs adotam abordagens diferentes, mas o objetivo é o mesmo: evitar erros de posição.
Para ver como os novos componentes de layout do Compose se beneficiam de data classes com desestruturação segura, confira nosso artigo sobre Grid, FlexBox e Media Query no Compose.
Perguntas frequentes
A desestruturação por nome funciona com Pair e Triple?
Pair e Triple continuam usando desestruturação posicional com a nova sintaxe de colchetes: val [primeiro, segundo] = pair. Como esses tipos representam tuplas genéricas sem nomes semânticos, a abordagem posicional com colchetes é a mais adequada.
Preciso reescrever todo meu código existente?
Nao. O modo only-syntax permite adotar a nova sintaxe gradualmente em código novo sem afetar código existente. A migração completa para a Fase 3 acontecerá em uma release futura com período de deprecação e ferramentas automáticas de migração no IntelliJ IDEA.
A desestruturação por nome é mais lenta que a posicional?
Nao. Ambas as formas geram o mesmo bytecode no JVM. A resolução por nome acontece em tempo de compilação — o compilador traduz val (val nome) = pessoa para val nome = pessoa.nome, sem overhead em runtime.
Posso misturar desestruturação por nome e por posição no mesmo arquivo?
Sim. Com a flag only-syntax, parênteses sem val continuam sendo posicionais e parênteses com val são por nome. Isso permite migração gradual dentro do mesmo arquivo ou módulo.