Comparação de slices em Go: como não perder desempenho e não enlouquecer
\n\nImagine a situação: você está escrevendo um serviço de alta carga em Go e precisa comparar dois slices de bytes ou strings. Parece simples, não é? Mas o diabo, como sempre, está nos detalhes. No Stack Overflow, a pergunta "Checking the equality of two slices" acumulou milhares de visualizações — e não é à toa. Neste artigo, vamos analisar quais armadilhas esperam o desenvolvedor, como escrever uma comparação correta e, principalmente, como fazer isso da forma mais rápida possível.
\n\nNão usaremos definições acadêmicas chatas. Em vez disso, exemplos práticos, benchmarks e dicas que você pode aplicar hoje mesmo. Vamos nessa!
\n\nPor que a comparação simples nem sempre é a correta?
\n\nEm Go, um slice não é um array. É um descritor que contém um ponteiro para o array subjacente, comprimento e capacidade. Portanto, o operador == não funciona para slices. Tentar escrever slice1 == slice2 resultará em um erro de compilação:
invalid operation: slice1 == slice2 (slice can only be compared to nil)\n\nIsso não é um bug, é uma feature. Os desenvolvedores do Go proibiram intencionalmente a comparação direta de slices porque isso pode levar a consequências não óbvias. Por exemplo, dois slices podem referenciar o mesmo array subjacente, mas ter comprimentos diferentes. Ou o contrário — arrays diferentes, mas dados idênticos.
\n\nEntão, o que fazer? Existem várias abordagens, e vamos analisar cada uma delas.
\n\nMétodo 1: Loop manual (básico, mas confiável)
\n\nA maneira mais óbvia é escrever uma função que percorre todos os elementos e os compara um a um. Isso dá controle total sobre o processo.
\n\nfunc EqualSlices(a, b []int) bool {\n if len(a) != len(b) {\n return false\n }\n for i := range a {\n if a[i] != b[i] {\n return false\n }\n }\n return true\n}\n\nPrós: transparência, sem dependências, possibilidade de personalização (por exemplo, comparação com tolerância para float).
\nContras: precisa ser escrito para cada tipo de dado.
Método 2: reflect.DeepEqual (universal, mas lento)
\n\nNa biblioteca padrão, existe a função reflect.DeepEqual, que consegue comparar qualquer estrutura de dados — slices, maps, structs. Fica assim:
import "reflect"\n\nfunc main() {\n a := []int{1, 2, 3}\n b := []int{1, 2, 3}\n fmt.Println(reflect.DeepEqual(a, b)) // true\n}\n\nParece ideal, mas há um detalhe: reflect.DeepEqual usa reflexão, e isso é sempre caro. Em slices pequenos, a diferença é imperceptível, mas se você tiver milhares de elementos — o desempenho cairá drasticamente.
Importante: reflect.DeepEqual também considera nil e um slice vazio como diferentes! Isso pode ser uma surpresa:
var a []int = nil\nb := []int{}\nfmt.Println(reflect.DeepEqual(a, b)) // false!\n\nMétodo 3: bytes.Equal para slices de bytes (ideal)
\n\nSe você trabalha com []byte, a melhor escolha é a função bytes.Equal da biblioteca padrão. Ela é escrita em assembly e é altamente otimizada:
import "bytes"\n\nfunc main() {\n a := []byte("hello")\n b := []byte("hello")\n fmt.Println(bytes.Equal(a, b)) // true\n}\n\nEsta é a maneira mais rápida para slices de bytes. Internamente, ela usa memcmp no nível C, o que proporciona um enorme ganho de desempenho.
Método 4: Comparação com crypto/subtle (para criptografia)
\n\nMenção especial merece o pacote crypto/subtle. Sua função ConstantTimeCompare é projetada para comparar dados secretos (senhas, tokens) sem vazamento de tempo. A comparação comum pode ser suscetível a ataques de timing:
import "crypto/subtle"\n\nfunc