«Onde está meu Set?» — o grito de alma do desenvolvedor Go
\n\nSe você veio para Go vindo de Python, JavaScript ou Java, mais cedo ou mais tarde se perguntará: «Onde, diabos, está meu amado Set?» — e isso é normal. No Stack Overflow, o tópico «Golang why don't we have a set datastructure» acumulou milhares de visualizações e dezenas de discussões acaloradas. Os desenvolvedores estão acostumados a um conjunto conveniente com elementos únicos, mas Go oferece apenas arrays, slices e maps. Neste artigo, vamos analisar a filosofia dos criadores do Go, aprender a emular um Set e até escrever nossa própria implementação minimalista.
Por que os criadores do Go não adicionaram Set à biblioteca padrão?
\n\nA resposta está no minimalismo e pragmatismo da linguagem. A equipe do Go (Rob Pike, Ken Thompson, Robert Griesemer) seguiu o princípio: «Se algo pode ser feito com uma única construção da linguagem — não crie entidades». Em Go, map não é apenas um dicionário, mas um contêiner universal. Ele já suporta a unicidade das chaves, que é a propriedade principal de um conjunto.
Filosofia «menos mágica — mais clareza»
\n\nAo contrário do Python, onde set é um tipo separado com seus próprios métodos (.add(), .union()), no Go decidiram não complicar a linguagem. Para que escrever uma estrutura separada se map[T]struct{} faz a mesma coisa? Isso economiza espaço na biblioteca padrão e reduz a barreira de entrada para iniciantes. O principal é aprender a usar corretamente o map como set.
Como emular Set em Go: três abordagens práticas
\n\nVamos para a prática. Aqui está como implementar um conjunto usando apenas os recursos padrão do Go.
\n\n1. A mais popular: map[type]struct{}
\n\nEsta é a abordagem padrão na comunidade Go. Por que struct{}? Porque a estrutura vazia não ocupa memória (0 bytes) — ideal para a marca de «existência» do elemento.
package main\n\nimport "fmt"\n\nfunc main() {\n // Cria um conjunto de números inteiros\n set := make(map[int]struct{})\n\n // Adiciona elementos\n set[1] = struct{}{}\n set[2] = struct{}{}\n set[3] = struct{}{}\n set[1] = struct{}{} // Duplicata é ignorada\n\n // Verificação de existência\n if _, exists := set[2]; exists {\n fmt.Println("2 está no conjunto")\n }\n\n // Remoção\n delete(set, 3)\n\n // Iteração\n for key := range set {\n fmt.Println(key)\n }\n}\n\nVantagens: alocação de memória zero para valores, simplicidade. Desvantagens: não há métodos .Add() ou .Contains() — tudo é feito através da sintaxe do map.
2. Tipo próprio com métodos
\n\nSe você precisa de uma API limpa, encapsule o map em uma struct:
\n\npackage main\n\nimport "fmt"\n\n// Set — conjunto genérico (Go 1.18+)\ntype Set[T comparable] struct {\n items map[T]struct{}\n}\n\n// NewSet cria um novo Set\nfunc NewSet[T comparable]() *Set[T] {\n return &Set[T]{items: make(map[T]struct{})}\n}\n\n// Add adiciona um elemento\nfunc (s *Set[T]) Add(item T) {\n s.items[item] = struct{}{}\n}\n\n// Contains verifica a presença\nfunc (s *Set[T]) Contains(item T) bool {\n _, ok := s.items[item]\n return ok\n}\n\n// Size retorna o tamanho\nfunc (s *Set[T]) Size() int {\n return len(s.items)\n}\n\nfunc main() {\n intSet := NewSet[int]()\n intSet.Add(10)\n intSet.Add(20)\n fmt.Println(intSet.Contains(10)) // true\n fmt.Println(intSet.Size()) // 2\n}\n\nIsso já se parece com uma set datastructure em Go completa. Os genéricos (generics) apareceram na versão 1.18, então o código funciona em projetos modernos.
\n\n3. Uso de bool em vez de struct{}
\n\nAlguns usam map[T]bool, mas isso gasta 1 byte por valor. Na maioria dos casos, a diferença é imperceptível, mas se você está escrevendo um serviço de alta carga — é melhor usar struct{}.