Post
Disponível em: English

Proposta do time do Go para 1.28: Set, Map ordenado e heap genérico na stdlib

E aí, pessoal!

O Go sempre foi minimalista na biblioteca padrão quando o assunto é estruturas de dados. Slice e map são os tijolos de construção, e por muito tempo isso foi suficiente. Mas desde que generics chegaram no Go 1.18 e iteradores no Go 1.23, ficou claro que era possível ter tipos de coleção de verdade na stdlib sem abrir mão da ergonomia da linguagem.

O Go Collections Working Group foi formado no final de 2025 exatamente para isso. E não é qualquer grupo: é o próprio time de desenvolvimento do Go. Jonathan Amsterdam, Alan Donovan, Robert Griesemer, Daniel Martí, Roger Peppe, Keith Randall e Ian Lance Taylor fazem parte do core da linguagem. Em setembro de 2026 eles publicaram a proposta umbrella #80590 para o Go 1.28 com vários pacotes novos.

Vou cobrir o que está sendo proposto e o que isso significa na prática.


O que está faltando hoje

Go tem container/heap, container/list e container/ring. Mas não tem:

  • Tipo Set nativo (a convenção é usar map[T]bool ou map[T]struct{})
  • Map com hash customizado (útil quando o tipo de chave não é comparável)
  • Map ordenado (para range queries)
  • Heap genérico (a API atual exige implementar uma interface com métodos Less, Swap e Len, o que é trabalhoso)

A proposta cobre tudo isso.


container/set.Set[T]

O tipo mais esperado. Um Set de elementos comparáveis, representado internamente como map[T]struct{}. A proposta é que ele se torne o Set padrão em novas APIs Go.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import "container/set"

s1 := set.Of("go", "rust", "python")
s2 := set.Of("go", "kotlin", "swift")

fmt.Println(s1.Contains("go"))       // true
fmt.Println(s1.Len())                // 3

// operações de conjunto
union := s1.Union(s2)
inter := s1.Intersection(s2)
diff  := s1.Difference(s2)

fmt.Println(union)  // {go, rust, python, kotlin, swift}
fmt.Println(inter)  // {go}
fmt.Println(diff)   // {rust, python}

Cada operação tem uma variante -With que muta o receptor ao invés de criar um novo set, útil quando alocação importa:

1
2
s1.UnionWith(s2)      // muta s1
s1.IntersectionWith(s2)

O grupo decidiu manter as duas variantes separadas depois de experiências ruins com a API do math/big.Int, onde um único método que ora muta ora não muta gerou confusão e bugs.


container/hash.Map[K,V] e container/hash.Set[T]

Para casos onde a chave não é comparável (slices, maps, interfaces com comparação personalizada) ou onde a comparação padrão do compilador não serve, há o container/hash:

1
2
3
4
5
6
7
8
9
10
11
12
import "container/hash"

// hash.Map com hasher customizado
m := hash.NewMap[[]byte, string](bytesHasher)
m.Set([]byte("chave"), "valor")
v, ok := m.Get([]byte("chave"))

// hash.Set
s := hash.NewSet[[]byte](bytesHasher)
s.Insert([]byte("go"))
s.Insert([]byte("golang"))
fmt.Println(s.Contains([]byte("go"))) // true

O Hasher é uma interface padrão introduzida no Go 1.27 via hash/maphash.Hasher que permite expressar funções de hash e relações de equivalência para tipos arbitrários.


container/ordered.Map[K,V]

Map com ordenação. A implementação atual usa árvore binária balanceada, mas a interface não depende disso.

O padrão hoje para iterar em ordem sobre um map é construir um map[K]V, coletar as chaves num slice e ordenar. Funciona bem na maioria dos casos, mas quando você precisa de range queries (todos os valores com chave entre X e Y), um map ordenado performa muito melhor:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import "container/ordered"

m := ordered.NewMap[string, int]()
m.Set("banana", 3)
m.Set("abacate", 1)
m.Set("caju", 5)
m.Set("manga", 2)

// iteração já vem em ordem
for k, v := range m.All() {
    fmt.Printf("%s: %d\n", k, v)
}
// abacate: 1
// banana: 3
// caju: 5
// manga: 2

container/heap/v2.Heap

A API atual do heap exige que você implemente heap.Interface:

1
2
3
4
5
6
7
8
9
10
11
12
13
type MinHeap []int

func (h MinHeap) Len() int           { return len(h) }
func (h MinHeap) Less(i, j int) bool { return h[i] < h[j] }
func (h MinHeap) Swap(i, j int)      { h[i], h[j] = h[j], h[i] }
func (h *MinHeap) Push(x any)        { *h = append(*h, x.(int)) }
func (h *MinHeap) Pop() any {
    old := *h
    n := len(old)
    x := old[n-1]
    *h = old[:n-1]
    return x
}

Com container/heap/v2:

1
2
3
4
5
6
7
8
9
import heapv2 "container/heap/v2"

h := heapv2.New(func(a, b int) bool { return a < b })
h.Push(5)
h.Push(1)
h.Push(3)

fmt.Println(h.Pop()) // 1
fmt.Println(h.Pop()) // 3

Muito menos boilerplate.


container/mapset

Para quem tem código existente que usa map[T]bool ou map[T]struct{} e não pode mudar a API, o pacote container/mapset oferece funções helper com a mesma semântica das operações de container/set.Set:

1
2
3
4
5
6
7
import "container/mapset"

a := map[string]struct{}{"go": {}, "rust": {}}
b := map[string]struct{}{"go": {}, "python": {}}

union := mapset.Union(a, b)
inter := mapset.Intersection(a, b)

Facilita a migração incremental.


Interfaces abstratas de coleção

A proposta define internamente interfaces genéricas (_AbstractCollection, _AbstractMap, _AbstractSet) que garantem consistência entre os tipos, mas não as exporta ainda. O plano é acumular experiência com os tipos concretos primeiro.

Isso permite escrever funções genéricas que funcionam com qualquer implementação de Set:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// exemplo interno da proposta
type _TakeSet[E any, S _TakeSet[E, S]] interface {
    All() iter.Seq[E]
    Delete(E) bool
}

func Take[S _TakeSet[E, S], E any](set S) (e E, found bool) {
    for e = range set.All() {
        found = true
        set.Delete(e)
        break
    }
    return
}

O F-bounded polymorphism (constraints que referenciam a própria interface) resolve o “binary method problem”: o método Union(S) S de dois tipos de Set distintos seria incompatível numa interface comum convencional, mas com esse padrão funciona.


Status e timeline

A proposta foi aberta em setembro de 2026 com milestone Go 1.28. Alguns pacotes já têm CLs associados em revisão. O container/set.Set tem o CL 745441, container/hash.Map tem o CL 612217, e o container/hash.Set tem o CL 741160.

A expectativa é que pelo menos container/set e container/heap/v2 cheguem no Go 1.28. Os demais dependem do andamento das revisões.


O que muda no dia a dia

Para a maioria dos casos o impacto mais imediato vai ser container/set.Set. Hoje, sets em Go são map[string]struct{} com _, ok := m[k] para verificar presença. Com container/set:

1
2
3
4
5
6
7
// antes
langs := map[string]struct{}{"go": {}, "rust": {}}
_, temGo := langs["go"]

// depois
langs := set.Of("go", "rust")
temGo := langs.Contains("go")

Mais legível, com operações de conjunto que hoje exigem loops manuais e código propenso a bugs.

O container/ordered.Map vai ser útil em casos específicos onde range queries importam. O heap genérico elimina um dos boilerplates mais chatos da stdlib.


Você já sentiu falta de alguma dessas estruturas em Go? Me conta nos comentários.

Até o próximo post!

Referências: