Ordenação de Structs em Go: Caminho Curto e Mágica Interna

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

Ordenação de Structs em Go: Como Escrever Código Curto e Não Enlouquecer

\n\n

Imagine: 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.

\n\n

O Problema: “Por que em Go não posso simplesmente escrever sort()?”

\n\n

Se você vem do Python, JavaScript ou Ruby, está acostumado a uma linha:

\n
users.sort(key=lambda u: u.age)
\n

Em 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:

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

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

A Solução: sort.Slice — Seu Kit de Ferramentas Elegante

\n\n

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

\n
sort.Slice(users, func(i, j int) bool {\n    return users[i].Age < users[j].Age\n})
\n

Pronto. Três linhas. Sem declarar novos tipos, sem métodos extras. Quer ordenar por nome? Basta trocar o campo:

\n
sort.Slice(users, func(i, j int) bool {\n    return users[i].Name < users[j].Name\n})
\n

E se precisar em ordem decrescente? É só inverter o sinal:

\n
sort.Slice(users, func(i, j int) bool {\n    return users[i].Age > users[j].Age // agora decrescente\n})
\n

Lindão. Mas como isso funciona? E qual é o truque?

\n\n

Por Baixo dos Panos: sort.Slice e Funções Anônimas

\n\n

Vamos entender o que acontece quando você chama sort.Slice. A função é declarada assim:

\n
func Slice(slice interface{}, less func(i, j int) bool)
\n

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

\n

Internamente, sort.Slice faz o seguinte:

\n
    \n
  1. 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.
  2. \n
  3. Extrai o tamanho via reflect.ValueOf(slice).Len().
  4. \n
  5. Cria um objeto auxiliar que implementa a interface sort.Interface dinamicamente. Esse objeto armazena uma referência ao slice original (via reflection) e à sua função less.
  6. \n
  7. Executa o algoritmo de ordenação padrão (quicksort do pacote sort). Quando o algoritmo precisa comparar dois elementos, ele chama sua função less. Quando precisa trocá-los de lugar, usa reflect.Swapper(slice).
  8. \n
\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

Blogs

Book Recommendations