Ordenação de Structs em Go: Como Escrever Código Curto e Não Enlouquecer
\n\nImagine: você tem um slice de structs e precisa ordená-lo por qualquer campo. Nome, idade, salário, data de nascimento — o que for. No Stack Overflow, a pergunta “What is the shortest way to simply sort an array of structs by (arbitrary) field names?” acumulou milhares de visualizações. E não é à toa. Go não tem um método “mágico” embutido como list.sort(key=lambda x: x.field) do Python. Mas existe uma maneira elegante e poderosa — sort.Slice. Neste artigo, não vamos apenas mostrar o código; vamos mergulhar nos bastidores: como a ordenação funciona, o que o runtime faz e como evitar as armadilhas comuns.
O Problema: “Por que em Go não posso simplesmente escrever sort()?”
\n\nSe você vem do Python, JavaScript ou Ruby, está acostumado a uma linha:
\nusers.sort(key=lambda u: u.age)\nEm Go, esse luxo não existe. A tipagem forte e a ausência de genéricos (até Go 1.18) forçavam você a escrever uma implementação separada de sort.Interface para cada tipo de struct. Era trabalhoso. Por exemplo:
type ByAge []User\nfunc (a ByAge) Len() int { return len(a) }\nfunc (a ByAge) Swap(i, j int) { a[i], a[j] = a[j], a[i] }\nfunc (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }\n\nfunc main() {\n users := []User{{"Alice", 30}, {"Bob", 25}}\n sort.Sort(ByAge(users))\n}\nFunciona, mas para cada campo é um novo tipo. E se você tiver 10 campos? O código vira um lençol. Dá vontade de algo mais compacto.
\n\nA Solução: sort.Slice — Seu Kit de Ferramentas Elegante
\n\nA partir do Go 1.8, o pacote sort ganhou a função Slice. Ela recebe um slice e uma função anônima (closure) que define a ordem. Aqui está — o caminho mais curto:
sort.Slice(users, func(i, j int) bool {\n return users[i].Age < users[j].Age\n})\nPronto. Três linhas. Sem declarar novos tipos, sem métodos extras. Quer ordenar por nome? Basta trocar o campo:
\nsort.Slice(users, func(i, j int) bool {\n return users[i].Name < users[j].Name\n})\nE se precisar em ordem decrescente? É só inverter o sinal:
\nsort.Slice(users, func(i, j int) bool {\n return users[i].Age > users[j].Age // agora decrescente\n})\nLindão. Mas como isso funciona? E qual é o truque?
\n\nPor Baixo dos Panos: sort.Slice e Funções Anônimas
\n\nVamos entender o que acontece quando você chama sort.Slice. A função é declarada assim:
func Slice(slice interface{}, less func(i, j int) bool)\nA primeira coisa que salta aos olhos é interface{}. A função aceita qualquer slice. Como ela descobre o tamanho e troca os elementos? A resposta é reflection (pacote reflect).
Internamente, sort.Slice faz o seguinte:
- \n
- Verifica se foi passado um slice. Se você passar algo que não é slice (como um array ou struct), o programa entra em pânico em tempo de execução. \n
- Extrai o tamanho via
reflect.ValueOf(slice).Len(). \n - Cria um objeto auxiliar que implementa a interface
sort.Interfacedinamicamente. Esse objeto armazena uma referência ao slice original (via reflection) e à sua funçãoless. \n - Executa o algoritmo de ordenação padrão (quicksort do pacote
sort). Quando o algoritmo precisa comparar dois elementos, ele chama sua funçãoless. Quando precisa trocá-los de lugar, usareflect.Swapper(slice). \n
O ponto-chave: sua função less captura o slice original do contexto externo. Isso é um closure. O compilador coloca a variável users no heap para que ela fique acessível mesmo após a saída da função m