Lorsque Go 1.18 a introduit les Génériques en 2022, il a apporté des paramètres de type génériques aux fonctions et structures, mais a laissé de côté les méthodes. Avec le lancement de Go 1.27, cette limitation a été supprimée. Les méthodes peuvent maintenant définir leurs propres paramètres de type sans les ajouter à la structure réceptrice.
Pourquoi ce changement a-t-il été introduit ? Si vous souhaitez créer un nœud de graphe qui contient une valeur générique, vous pourriez l'implémenter ainsi:
type Node[T any] struct {
value T
}
Imaginez ajouter une méthode Map qui transforme un nœud de type T en un nœud d'un autre type U. Avant Go 1.27, vous étiez contraint d'ajouter U directement à la structure Node elle-même:
type Node[T any, U any] struct {
value T
}
func (n *Node[T, U]) Map() Node[T, U] {
// ...
}
Ajouter U à la structure est un mauvais design car U est un paramètre de type spécifique à Map. Même si d'autres méthodes n'utiliseraient pas U, elles devaient quand même le garder dans leurs déclarations de récepteur. La seule solution de contournement était de implémenter Map comme une fonction au niveau du paquet plutôt que comme une méthode, car les fonctions pouvaient définir leurs propres paramètres de type. Mais les méthodes ne pouvaient pas, ce qui menait à des conceptions d'API de code maladroites et non idiomatiques. À partir de Go 1.27, U peut être défini exclusivement sur la méthode Map:
func (n *Node[T]) Map[U any]() Node[U] {
// ...
}
Pourquoi cela a-t-il pris si longtemps ? La réponse réside dans la manière dont les génériques sont implémentés dans Go. Le compilateur gère les génériques principalement par monomorphisation, ce qui signifie qu'il créera une copie des structures ou fonctions génériques pour chaque type spécifique avec lequel ils sont utilisés. Parce qu'à un moment donné, le concept abstrait des génériques doit être traduit en code machine direct. Cependant, le système d'interfaces de Go fonctionne à l'exécution. Le type spécifique d'une valeur passée en paramètre d'interface est résolu pendant l'exécution du programme. Cette distribution dynamique entre en conflit avec les génériques résolus à la compilation. Considérez ce qui se passerait si nous voulions déclarer notre méthode Map dans une interface:
type Mapper interface {
Map T any, U any U
}
Pour que cela fonctionne, le temps d'exécution aurait besoin soit d'un compilateur Just-In-Time pour générer du code machine pour des types spécifiques à la volée, soit de créer ces copies pour chaque type possible à l'avance, entraînant un binaire massivement gonflé. Finalement, l'équipe de Go a décidé de séparer les méthodes génériques des interfaces. Go 1.27 permet des méthodes génériques sur des types concrets, mais pas sur des interfaces. Cette distinction explique pourquoi l'exemple ci-dessus ne compile pas et pourquoi les discussions à ce sujet ont pris du temps.
Source: Hacker News · Résumé par HeadlinesBriefing