Go n'a jamais livré de type ensemble, et toute base de code un peu sérieuse a fini par réécrire le même utilitaire map[string]struct{} pour compenser. Cela pourrait s'arrêter avec Go 1.28. Une proposition chapeau déposée par Alan Donovan le vingt huit juillet rassemble le travail d'un groupe collections formé fin 2025, et elle esquisse une famille cohérente : container/set pour les éléments comparables, container/hash pour le hachage et l'égalité sur mesure, container/ordered adossé à des arbres équilibrés, et un container/heap/v2 générique. Les fondations existent déjà, puisque l'interface maphash.Hasher fait partie de Go 1.27. Rien n'est accepté, et les choix de conception sont plus intéressants que la liste des paquets.
The short answer
Une proposition chapeau déposée le vingt huit juillet par Alan Donovan rassemble le travail d'un groupe collections formé fin 2025. Elle couvre container/set pour les éléments comparables, container/hash avec des types Map et Set pilotés par un hacheur sur mesure, container/ordered adossé à des arbres binaires équilibrés, container/mapset pour les ensembles écrits à l'ancienne, et un container/heap/v2 générique. La brique habilitante, l'interface maphash.Hasher, est déjà livrée dans Go 1.27. La conception mise sur la transparence : set.Set est défini à partir de map[E]struct pour que len, l'indexation et range continuent de fonctionner. Rien n'est accepté.
Demandez à une salle de développeurs Go ce qu'ils ont écrit le plus souvent et quelqu'un répondra un ensemble. Pas un ensemble subtil. Le map[string]struct{} de quatre lignes avec un Add et un Contains, réécrit dans chaque service parce que le copier a toujours coûté moins cher que se mettre d'accord sur une bibliothèque. Les génériques sont arrivés en Go 1.18 et n'ont pas mis fin à cela, car une enveloppe générique se lit encore moins bien que la map native qu'elle enveloppe.
Ce qui est réellement proposé
Le ticket chapeau, déposé le vingt huit juillet par Alan Donovan, est une carte de plusieurs propositions concrètes plutôt qu'une API unique. Les pièces :
container/set.Set[T] est l'ensemble canonique pour les éléments comparables. container/hash fournit Map[K,V] et Set[T] pour les types d'éléments qui ont besoin d'une fonction de hachage et d'une relation d'équivalence sur mesure. container/ordered.Map[K,V] est une association adossée à un arbre binaire équilibré, dont le parcours suit l'ordre des clés. container/mapset regroupe des fonctions utilitaires pour les ensembles écrits à l'ancienne. container/heap/v2.Heap est la refonte générique de l'API de tas.
Sous l'ensemble se trouve hash/maphash.Hasher, qui n'est plus une proposition du tout : il est livré dans Go 1.27, la version attendue ce mois ci.
Le groupe de travail à l'origine de tout cela s'est formé fin 2025 et se lit comme la liste des personnes qui ont façonné le langage : Jonathan Amsterdam, Alan Donovan, Robert Griesemer, Daniel Martí, Roger Peppe, Keith Randall et Ian Lance Taylor.
La décision de transparence est la plus intéressante
Le set.Set recommandé est défini de façon transparente à partir de map[E]struct{}.
Ce seul choix explique l'essentiel de l'API. Comme le type sous jacent est une map, len(s) fonctionne. L'accès aux éléments fonctionne. range fonctionne. Vous n'appelez pas s.Len() et s.Iterate() sur une enveloppe qui n'existe que pour cacher une map que vous saviez déjà lire.
Le coût est réel et mérite d'être nommé : la représentation fait partie de l'API publique, elle ne pourra donc jamais changer. C'est une contrainte définitive acceptée en échange d'un type qui se comporte comme quelque chose que les développeurs Go connaissent déjà. Pour une bibliothèque standard qui doit vivre indéfiniment, la familiarité est en général le meilleur pari, et c'est le même instinct qui a maintenu error en interface à une seule méthode.
Les hacheurs, et le type que vous ne maîtrisez pas
maphash.Hasher[T] est ce qui rend container/hash possible. L'interface réunit deux opérations : Hash(hash *maphash.Hash, value T) mélange une valeur dans un hachage en cours, et Equal(T, T) bool décide si deux valeurs sont identiques. ComparableHasher couvre le cas ordinaire où == signifie déjà ce que vous voulez.
L'exemple canonique est la chaîne insensible à la casse. Votre Hash écrit la forme en minuscules dans le hachage en cours, votre Equal compare les formes en minuscules, et un container/hash.Set[string] construit avec ce hacheur traite Bob et bob comme un seul élément. Rien dans la collection n'a besoin de savoir pourquoi.
C'est la pièce qui débloque le reste, et c'est pourquoi le calendrier tient : avec Hasher en 1.27, les propositions de collections pour la 1.28 ont un socle au lieu d'inventer chacune sa convention de hachage.
Des détails d'API qui trahissent des gens ayant déjà écrit ce code
Deux conventions de la proposition ressortent.
Les méthodes de modification indiquent si elles ont changé la taille de la collection. C'est toute la différence entre s.Add(x) en instruction et if s.Add(x) { ... } en test de doublon, et cela supprime le motif tester puis ajouter qui coûte deux recherches.
Les opérations ensemblistes existent en variante fonctionnelle et en variante modifiante. Union, Intersection et Difference peuvent soit renvoyer un nouvel ensemble, soit modifier le receveur, parce que les deux usages sont légitimes et qu'en choisir un seul imposerait une allocation évitable à la moitié des appelants.
L'approche annoncée pour l'implémentation est volontairement simple : satisfaire d'abord l'API et les attentes asymptotiques de la façon la plus directe possible. Personne ne livre une table de hachage optimisée à la main en première version, et c'est le bon ordre.
Que faire concrètement
Rien pour l'instant, et il vaut mieux le dire clairement. Il s'agit d'une proposition ouverte avec une étiquette de version, pas d'une note de version. Go 1.28 sortira début 2027 si le rythme habituel tient, et les sous propositions doivent encore passer la revue chacune de leur côté.
Ce qui vaut la peine aujourd'hui, c'est de lire le ticket 80590 et ses sous tickets tant que les commentaires comptent encore, surtout si votre base de code héberge une de ces bibliothèques d'ensembles maison chargées d'opinions. Le groupe de travail demande explicitement des retours sur une conception qui, une fois livrée, ne pourra plus changer.
Sources et pour aller plus loin
- Proposition Go : container/... types de collections génériques (ticket 80590)
- Proposition Go : container/set, un type ensemble générique (ticket 69230)
- Proposition Go : container/hash.Map avec fonction de hachage sur mesure (ticket 69559)
- Anton Zhiyanov : Go proposal, Hashers
- Notes de version de Go 1.27
- Documentation du paquet hash/maphash
Questions fréquentes
Est ce accepté, et quand cela pourrait il sortir ?
Ce n'est pas accepté. Ce qui existe est une proposition chapeau, le ticket golang/go 80590, déposé le vingt huit juillet 2026 et étiqueté pour la version 1.28, qui rassemble plusieurs sous propositions concrètes passant chacune par le processus de revue habituel. Certaines sont plus anciennes que le chapeau : la proposition container/set est le ticket 69230 et celle sur container/hash.Map le ticket 69559, tous deux ouverts depuis 2024. Une étiquette de version sur une proposition Go est une cible, pas un engagement. Go 1.28 sortirait début 2027 au rythme habituel de six mois, et une issue plausible est qu'une partie de cette famille arrive à cette date et qu'une autre glisse ou change de forme après la revue publique.
Pourquoi définir set.Set à partir de map[E]struct{} plutôt que de cacher la représentation ?
Parce que la définition transparente vous donne gratuitement les fonctionnalités du langage. Un ensemble défini comme map[E]struct{} accepte len(set), l'accès direct aux éléments et le parcours avec range en syntaxe native, au lieu d'exiger une méthode pour chacun. C'est l'écart d'ergonomie que les génériques seuls n'avaient pas comblé : un type enveloppe avec une méthode Len() se lit moins bien que len(s) et compose moins bien avec le reste du langage. Le coût, c'est que la représentation fait partie de l'API et ne pourra donc plus changer. Le groupe de travail semble avoir jugé qu'un comportement prévisible et familier vaut mieux que la liberté de remplacer l'implémentation, un arbitrage très caractéristique de Go.
Qu'est ce que maphash.Hasher et pourquoi le reste en dépend il ?
C'est l'interface qui permet à une collection de hacher et de comparer un type qu'elle ne maîtrise pas. La forme est minimale : un Hasher[T] fournit Hash(hash *maphash.Hash, value T) pour mélanger une valeur dans un hachage en cours, et Equal(T, T) bool pour comparer deux valeurs. Un ComparableHasher couvre les types qui fonctionnent déjà avec l'égalité ordinaire. Il est livré dans Go 1.27, la version attendue ce mois ci, et c'est pourquoi le travail sur les collections peut être proposé maintenant. L'exemple classique est l'ensemble de chaînes insensible à la casse : votre Hash met en minuscules avant d'écrire, votre Equal met en minuscules avant de comparer, et container/hash construit un ensemble avec cette sémantique sans rien savoir de votre type.
Qu'apporte container/ordered qu'une map n'apporte pas ?
L'ordre. Une map Go n'a pas d'ordre de parcours défini, délibérément, et la parade standard consiste à collecter les clés dans une tranche et à les trier chaque fois qu'il faut parcourir dans l'ordre. container/ordered.Map est proposé comme une association adossée à un arbre binaire équilibré, qui garde en permanence les éléments dans l'ordre des clés. Cela change l'asymptotique d'un motif courant : au lieu d'un tri en O(n log n) à chaque parcours, vous payez O(log n) à l'insertion et le parcours ordonné devient gratuit. Cela rend aussi exprimables les requêtes par intervalle, ce qu'une table de hachage ne peut pas faire. La contrepartie est une recherche ponctuelle plus lente, l'arbitrage habituel entre arbre et table de hachage.
Les paquets container existants disparaissent ils ?
Non, et la proposition est prudente sur ce point. Le travail sur le tas est proposé sous le chemin container/heap/v2, une version distincte plutôt qu'un remplacement sur place, mécanisme que la bibliothèque standard utilise quand une API a besoin d'une refonte générique sans casser le code qui importe déjà l'ancienne. Un paquet container/mapset est également proposé, avec des fonctions utilitaires pour les ensembles écrits à l'ancienne, en map[E]bool ou map[E]struct{} : c'est une aide à la migration, pas une nouvelle structure de données. La promesse de compatibilité de Go veut que rien ne soit retiré de la bibliothèque standard, donc l'issue réaliste est une coexistence, le code neuf utilisant les nouveaux paquets.