Generics en Go : pourquoi et comment les utiliser

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Introduction : que sont les Generics et pourquoi sont-ils apparus dans Go



Pendant longtemps, la communauté Go s'est passée de generics (types génériques). Les développeurs utilisaient interface{} et le transtypage manuel, ce qui conduisait à un code lourd et à des erreurs lors de l'exécution. Avec la sortie de la version Go 1.18 (février 2022), les generics ont été officiellement ajoutés au langage — l'une des fonctionnalités les plus attendues.



Les Generics (ou polymorphisme paramétrique) permettent d'écrire des fonctions et des structures de données qui fonctionnent avec n'importe quel type, tout en conservant un typage strict lors de la compilation. Cela signifie que vous pouvez écrire un seul algorithme (par exemple, un tri ou une recherche) et l'utiliser pour int, string, des structures personnalisées — sans duplication de code et sans perte de sécurité des types.



Dans cet article, nous verrons pourquoi les generics sont nécessaires en Go, comment les utiliser correctement et quels pièges il faut prendre en compte.



Partie principale : comment fonctionnent les Generics en Go



1. Syntaxe des paramètres de type



La syntaxe de base des generics en Go est la suivante : après le nom de la fonction ou du type, entre crochets, on indique les paramètres de type. Examinons l'exemple le plus simple — une fonction qui retourne la valeur passée (analogue de identity) :



package main

import "fmt"

// Fonction générique : T est un paramètre de typefunc Print[T any](value T) { fmt.Println(value)}

func main() { Print[int](42) // indication explicite du type Print[string]("Go") // indication explicite du type Print("Go 1.18") // inférence de type implicite (type inference)}


Le mot-clé any est un alias pour interface{}. Il signifie que la fonction accepte une valeur de n'importe quel type. Cependant, la véritable puissance des generics se révèle avec l'utilisation de contraintes (constraints).



2. Contraintes (constraints) et interfaces



Les contraintes permettent d'indiquer quelles opérations peuvent être effectuées avec le paramètre de type. Par exemple, pour additionner deux nombres, il faut garantir que le type prend en charge les opérateurs +, -, *, etc. En Go, on utilise pour cela des interfaces spéciales du package golang.org/x/exp/constraints ou des interfaces personnalisées :



package main

import ( "fmt" "golang.org/x/exp/constraints")

// Contrainte : T doit prendre en charge la comparaisonfunc Max[T constraints.Ordered](a, b T) T { if a > b { return a } return b}

func main() { fmt.Println(Max(10, 20)) // 20 fmt.Println(Max("apple", "banana")) // banana // fmt.Println(Max([]int{1}, []int{2})) // Erreur de compilation : les slices ne sont pas ordonnés}


L'interface intégrée constraints.Ordered inclut les types qui prennent en charge les opérateurs <, <=, >, >= : tous les types numériques et string.



Vous pouvez également créer vos propres contraintes :



// Contrainte personnalisée : le type doit avoir une méthode String()type Stringer interface {    String() string}

func Describe[T Stringer](v T) string { return "Value: " + v.String()}


3. Structures et méthodes génériques



Les generics en Go fonctionnent non seulement avec les fonctions, mais aussi avec les types personnalisés. Examinons un exemple classique — une pile générique :



package main

import "fmt"

// Structure générique Stacktype Stack[T any] struct { items []T}

// Push ajoute un élément à la pilefunc (s *Stack[T]) Push(item T) { s.items = append(s.items, item)}

// Pop retire un élément de la pilefunc (s *Stack[T]) Pop() (T, bool) { if len(s.items) == 0 { var zero T return zero, false } last := s.items[len(s.items)-1] s.items = s.items[:len(s.items)-1] return last, true}

func main() {

Blogs

Book Recommendations