Les généricités ou generics ont été introduites dans Go à partir de la version 1.18. Pendant longtemps, Go a été considéré comme un langage conservateur, où, pour des raisons de simplicité, les generics ont été exclus, mais avec l'augmentation du nombre de projets et l'évolution de l'écosystème, il est devenu nécessaire d'écrire des fonctions et des structures universelles. Cela est particulièrement critique pour les structures de conteneurs, les algorithmes de traitement de collections et le code d'infrastructure.
Problème : Avant l'apparition des generics, il était nécessaire de dupliquer le code ou d'utiliser des interfaces vides (interface{}), ce qui entraînait une perte de sécurité de type et une diminution des performances, ainsi qu'une complexité accrue pour le débogage.
Solution : Les generics sont implémentés en Go à l'aide de paramètres de type, qui sont spécifiés entre crochets pour les fonctions et les types. Avec les contraintes, il est possible de restreindre les paramètres de type acceptables. Cela permet d'écrire des fonctions génériques sans perte de sécurité de type.
Exemple de code :
package main import "fmt" type Adder[T any] func(a, b T) T func Sum[T any](slice []T, add Adder[T]) T { var result T for _, v := range slice { result = add(result, v) } return result } func intAdder(a, b int) int { return a + b } func main() { nums := []int{1, 2, 3, 4} sum := Sum(nums, intAdder) fmt.Println(sum) }
Caractéristiques clés :
Peut-on utiliser des opérations arithmétiques (+, -, *, /) pour n'importe quel paramètre de type T dans les generics ?
Non. Le compilateur Go ne sait pas si le type du paramètre supporte l'arithmétique. Pour cela, il faut spécifier une contrainte, par exemple, une interface avec des contraintes d'opérateurs à partir de Go 1.18+.
Exemple de code :
type Addable interface { int | float64 | uint } func Sum[T Addable](slice []T) T { var result T for _, v := range slice { result += v } return result }
Les conteneurs génériques peuvent-ils contenir des méthodes avec un comportement différent pour différents types ?
Non. Les méthodes des structures ou fonctions génériques sont identiques pour tous les types, sauf si l'on utilise un switch de type à l'intérieur de la méthode. Le comportement doit être défini par le biais de contraintes ou être purement paramétré.
Peut-on créer des types avec des paramètres de type (types génériques) au niveau du paquet, et pas seulement des fonctions ?
Oui, à partir de Go 1.18, il est possible de créer à la fois des fonctions génériques et des types de structures génériques :
type Stack[T any] struct { items []T } func (s *Stack[T]) Push(v T) { s.items = append(s.items, v) }
Dans une bibliothèque pour le travail avec des collections, une fonctionnalité Map universelle a été réalisée via interface{} :
Avantages :
Inconvénients :
Dans le même projet, ils sont passés aux generics et ont défini des contraintes via des interfaces :
Avantages :
Inconvénients :