Menu

Génériques en Golang : paramètres de type, contraintes et exemples

Les génériques (Go 1.18+) permettent à une fonction ou à un type de fonctionner avec de nombreux types tout en restant sûrs au niveau du typage. Découvrez les paramètres de type, les contraintes any, comparable et cmp.Ordered, les contraintes personnalisées avec ~, les types génériques, et quand ne pas les utiliser.

Cette page contient des éditeurs exécutables - modifiez, exécutez et voyez la sortie instantanément.

Une fonction générique

Les paramètres de type se placent entre crochets avant les paramètres ordinaires. Chacun a une contrainte qui indique les types autorisés.

T et U sont déduits des arguments, donc vous écrivez rarement Map[int, string](...). L'inférence part des arguments de la fonction ; elle ne peut pas déduire un type qui n'apparaît que dans le résultat. Pour func Zero[T any]() T, vous devez écrire Zero[int]().

Avant Go 1.18, cette fonction devait être écrite une fois par type, ou prendre un []interface{} et perdre la sécurité du typage.

Contraintes

Une contrainte est une interface. Elle limite les types utilisables et, en échange, indique au compilateur quelles opérations sont permises sur les valeurs de ce type.

ContrainteAutoriseVous permet d'utiliser
anytous les typesaffectation, passage, stockage
comparableles types qui supportent ====, !=, clés de map
cmp.Ordered (Go 1.21)entiers, flottants, chaînes<, <=, >, >=, plus ==
une interface avec des méthodesles types qui ont ces méthodesappeler les méthodes
une union de types comme ~int | ~float64exactement ces typesles opérateurs que tous supportent

Avec any au lieu de comparable, v == target ne compile pas : invalid operation: v == target (incomparable types in type set). C'est la contrainte qui rend l'opérateur légal.

Ces deux fonctions existent déjà : slices.Index et slices.Max. Les packages standard slices, maps et cmp couvrent la plupart des besoins génériques du quotidien, donc regardez-les avant d'écrire les vôtres.

Contraintes personnalisées avec des unions de types et ~

Une contrainte peut lister des types avec |. Les opérateurs autorisés sont ceux que tous les types listés supportent.

Le tilde compte. ~int64 signifie « tout type dont le type sous-jacent est int64 », donc le type nommé Cents est accepté. Écrit simplement int64, Sum([]Cents{...}) échoue avec Cents does not satisfy Number (possibly missing ~ for int64 in Number).

Les interfaces contenant des unions de types ne peuvent servir que de contraintes, jamais de types de variables ordinaires. var n Number ne compile pas.

Le package golang.org/x/exp/constraints propose des contraintes toutes prêtes Integer, Float et Signed. Il est hors de la bibliothèque standard, donc l'environnement d'exécution d'ici ne peut pas l'importer ; cmp.Ordered est la contrainte standard.

Types génériques

Les structs, slices, maps et autres types peuvent aussi avoir des paramètres de type. Les méthodes d'un type générique utilisent les paramètres du receveur.

Deux détails de cet exemple reviennent constamment :

  • La valeur zéro de T. var zero T est la façon de renvoyer « rien » depuis du code générique. Il n'existe pas de T{} ni de nil qui fonctionne pour tous les types.
  • L'instanciation. Un type générique doit être instancié avant usage : Stack[string], pas Stack. Dans ses propres méthodes, le receveur s'écrit Stack[T].

Go 1.24 a ajouté les alias de types génériques : type Set[T comparable] = map[T]struct{} compile désormais.

Les limites que vous rencontrerez

  • Pas de paramètres de type sur les méthodes. func (s *Stack[T]) Map[U any](...) n'est pas permis. Utilisez une fonction de haut niveau.
  • Pas de spécialisation. Vous ne pouvez pas écrire une implémentation séparée pour T = string. Un type switch sur any(v) dans la fonction est le contournement, et en général le signe que les génériques ne sont pas le bon outil.
  • Pas de contrainte d'opérateur par nom. Vous ne pouvez pas dire « tout type qui a une méthode + » ; vous listez les types dans une union.
  • L'accès aux champs via une contrainte ne fonctionne pas. Même si chaque type d'une union a un champ ID, v.ID n'est pas permis. Mettez plutôt une méthode dans la contrainte.

Quand ne pas utiliser les génériques

Les génériques conviennent aux types conteneurs (piles, ensembles, caches, arbres) et aux algorithmes sur des slices et des maps qui font la même chose quel que soit le type des éléments. Ils ne conviennent pas partout.

  • Un seul type concret. Si la fonction n'est jamais appelée qu'avec des []User, une version générique ajoute un coût de lecture et rien d'autre.
  • Un comportement qui dépend du type. C'est le rôle des interfaces. Une fonction qui prend un io.Writer est plus simple qu'une fonction qui prend [T io.Writer], et fonctionne pareil.
  • Remplacer des paramètres interface. func Print[T fmt.Stringer](v T) n'apporte rien par rapport à func Print(v fmt.Stringer).

La recommandation de l'équipe Go elle-même : écrivez d'abord le code pour un type précis, et passez aux paramètres de type quand vous vous surprenez à écrire le même code une deuxième fois avec seulement les types qui changent.

Erreurs courantes

  • Utiliser any là où comparable ou cmp.Ordered est nécessaire. Le compilateur rejette == ou < sur un paramètre de type any.
  • Oublier ~. Les types définis par l'utilisateur comme type ID int ne satisfont pas une contrainte int simple.
  • Renvoyer nil pour un paramètre de type. Ce n'est pas permis, sauf si la contrainte limite T à des types de type pointeur. Renvoyez une valeur zéro.
  • Écrire une fonction générique qui existe déjà. Regardez d'abord slices, maps et cmp.

Questions fréquentes

Go a-t-il des génériques ?

Oui, depuis Go 1.18 (mars 2022). Les fonctions et les types peuvent déclarer des paramètres de type entre crochets : func Map[T, U any](s []T, f func(T) U) []U. Chaque paramètre de type a une contrainte, c'est-à-dire une interface qui décrit ce que le type doit permettre.

Quelle est la différence entre any et comparable dans les génériques Go ?

any autorise tous les types mais ne vous permet presque rien avec les valeurs, à part les affecter, les passer et les stocker. comparable n'autorise que les types qui supportent == et !=, ce qu'il vous faut pour des clés de map ou des tests d'égalité. Pour < et >, utilisez cmp.Ordered.

Que signifie le tilde (~) dans une contrainte Go ?

~T signifie « tout type dont le type sous-jacent est T ». ~int correspond à int et aussi à type Celsius int. Sans le tilde, int dans une contrainte ne correspond qu'à int lui-même, donc les types définis par l'utilisateur seraient rejetés.

Les méthodes Go peuvent-elles avoir des paramètres de type ?

Non. Une méthode peut utiliser les paramètres de type de son type receveur (func (s *Stack[T]) Push(v T)), mais elle ne peut pas en déclarer de nouveaux. Écrivez plutôt une fonction générique de haut niveau : func Map[T, U any](s *Stack[T], f func(T) U) *Stack[U].

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER