Québec, Canada

403-1381 1re Avenue

+1 581.849.27.96

bdgouthiere@gmail.com

Méthodes génériques en Go : ce que la 1.27 change, et ce qu'elle refuse encore

Ou : Comment une méthode a enfin le droit de choisir ses types, à condition de ne le dire à aucune interface

Go 1.27 est sorti le 19 août 2026, et sa nouveauté la plus attendue tient en une phrase des notes de version : une déclaration de méthode peut désormais déclarer ses propres paramètres de type. Depuis l’arrivée des génériques en Go 1.18, une fonction pouvait être générique, une méthode non. Résultat : les aides génériques qui appartenaient logiquement à un type vivaient au niveau du paquet, avec le type passé en argument, comme un invité qu’on fait attendre sur le palier. C’est réglé. Il reste une exception, et elle compte plus qu’elle n’en a l’air : les interfaces n’ont toujours pas droit aux génériques, et une méthode générique ne satisfait aucune interface. Pour qui teste son code derrière des interfaces, c’est là que tout se joue.

Méthodes génériques en Go 1.27 : syntaxe, limites et code testé

Le client JSON qui n’avait pas le droit d’être une méthode

Prends le cas le plus banal d’un service qui parle à une API : un client HTTP qui renvoie du JSON décodé dans le type de ton choix. Jusqu’à Go 1.26, la version générique devait être une fonction de paquet.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// Before Go 1.27: a package-level function, the client passed as an argument.
func GetJSON[T any](ctx context.Context, c *Client, path string) (T, error) {
	var v T
	b, err := c.raw(ctx, path)
	if err != nil {
		return v, err
	}
	return v, json.Unmarshal(b, &v)
}

user, err := GetJSON[User](ctx, client, "/users/1")

Ça marche, mais la fonction n’est pas là où on la cherche. L’autocomplétion de ton éditeur ne la propose pas après client., et le paquet se remplit de fonctions qui prennent toutes un *Client en deuxième position. Avec Go 1.27, la même chose s’écrit là où elle appartient.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// Go 1.27: the same thing, where it belongs.
func (c *Client) Get[T any](ctx context.Context, path string) (T, error) {
	var v T
	b, err := c.raw(ctx, path)
	if err != nil {
		return v, err
	}
	return v, json.Unmarshal(b, &v)
}

user, err := client.Get[User](ctx, "/users/1")

L’argument de type reste explicite, Get[User], parce que T n’apparaît que dans le type de retour : le compilateur n’a rien pour le deviner. La bibliothèque standard a fait le même déménagement : math/rand/v2 ajoute une méthode (*Rand).N[Int intType](n Int) Int, là où il n’existait qu’une fonction de paquet N1.

Lire de gauche à droite

Le second bénéfice se voit sur les traitements enchaînés. Une méthode générique peut introduire un type que le receveur ne connaît pas, ce qui permet enfin d’écrire un Map qui change le type des éléments.

1
2
3
4
5
6
7
8
// Before: Map(Map(l, f), g), read from the inside out.
func Map[E, R any](l List[E], f func(E) R) List[R] { /* ... */ }

// After: l.Map(f).Map(g), read from left to right.
func (l List[E]) Map[R any](f func(E) R) List[R] { /* ... */ }

labels := Map(Map(ids, times10), strconv.Itoa) // Go 1.26
labels := ids.Map(times10).Map(strconv.Itoa)   // Go 1.27

Les deux lignes font exactement la même chose, et mon test le vérifie. La seconde se lit dans l’ordre où elle s’exécute, ce qui n’est pas un luxe le jour où la chaîne compte cinq étapes et où tu la relis à deux heures du matin. Le billet de l’équipe Go qui présente la fonctionnalité prend d’ailleurs un exemple très voisin2.

La règle qui n’a pas bougé : pas de générique dans une interface

Voici l’exception, et le compilateur l’énonce sans détour. Une méthode d’interface ne peut pas déclarer de paramètres de type.

1
2
3
4
type Getter interface {
	Get[T any](ctx context.Context, path string) (T, error)
}
// interface method must have no type parameters

Plus subtil : même une interface non générique, qui décrit exactement la version instanciée de ta méthode, ne correspond pas.

1
2
3
4
5
6
7
8
type UserGetter interface {
	Get(ctx context.Context, path string) (User, error)
}

var _ UserGetter = (*Client)(nil)
// *Client does not implement UserGetter (wrong type for method Get)
//     have Get[T any](context.Context, string) (T, error)
//     want Get(context.Context, string) (User, error)

Les notes de version le disent en une ligne : les méthodes d’interface ne peuvent pas déclarer de paramètres de type, et une méthode générique ne peut pas implémenter une méthode d’interface1. La raison tient à la façon dont Go compile. Un paquet qui reçoit une valeur d’interface ne sait pas quel type concret se cache derrière, ni avec quels arguments de type la méthode serait appelée. Pour qu’une méthode d’interface générique fonctionne, le compilateur devrait préparer toutes les instanciations possibles d’avance, et il y en a une infinité2. Les méthodes concrètes n’ont pas ce problème : le type du receveur est connu à la compilation, l’appel se résout sur place.

Ce que ça change pour tes tests

Cette règle a une conséquence directe si tu testes ton code comme dans l’article sur les interfaces et le mocking : on remplace une dépendance par un faux qui satisfait la même interface. Avec une méthode générique, il n’y a pas d’interface possible. Ton Client.Get[T] ne se remplace par rien.

La solution consiste à séparer les étages. L’interface décrit le cœur non générique, celui qui fait vraiment le travail réseau, et la méthode générique s’empile par-dessus.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// Fetcher is the non-generic core: this is what tests replace.
type Fetcher interface {
	Fetch(ctx context.Context, path string) ([]byte, error)
}

// API adds the typed layer on top. The generic method never needs an interface.
type API struct{ f Fetcher }

func (a API) Get[T any](ctx context.Context, path string) (T, error) {
	var v T
	b, err := a.f.Fetch(ctx, path)
	if err != nil {
		return v, err
	}
	return v, json.Unmarshal(b, &v)
}

Le test remplace Fetcher par une table de réponses en mémoire, et la méthode générique est testée sans réseau, avec son vrai décodage JSON. Pour le client HTTP lui-même, httptest fournit un faux serveur, et c’est ce que mes tests utilisent.

DevOps Dave : J’ai migré toutes nos fonctions d’aide en méthodes génériques. Le paquet est magnifique.

Security Sarah : Et le service de paiement, tu le testes comment maintenant ?

DevOps Dave : Comme avant, avec l’interface PaymentClient et le faux.

Security Sarah : Celle qui déclarait Charge ? Tu en as fait une méthode générique ce matin.

DevOps Dave : … Le paquet était magnifique.

Trois détails vérifiés à la main

La proposition et les notes de version ne disent pas tout, alors j’ai testé ce qui m’intéressait sous Go 1.27.1. D’abord, une méthode générique est promue par l’incorporation de structure : un AuthClient qui incorpore *Client expose Get[User] comme si elle était la sienne. Ensuite, une valeur de méthode doit être instanciée : getUser := client.Get[User] fonctionne et donne une fonction ordinaire, conformément à la proposition3. Enfin, le paquet reflect ne voit pas les méthodes génériques : MethodByName("Get") ne trouve rien, pour la même raison qu’il ne sait pas instancier une fonction générique3. Si ton code découvre des méthodes par réflexion, celles-là lui échapperont.

Un dernier piège attend les projets existants. La fonctionnalité dépend de la directive go de ton go.mod, pas de la version de l’outil installé. Un module resté en go 1.26 refuse de compiler avec un message clair : generic method requires go1.27 or later (-lang was set to go1.26; check go.mod).

Le reste de Go 1.27, en une phrase chacun

Les méthodes génériques ne sont pas seules. Une clé de littéral de structure peut désormais désigner un champ promu par incorporation, Article{ID: 7} au lieu de Article{Base: Base{ID: 7}}. L’inférence de type des fonctions génériques s’applique dans plus de contextes d’affectation. Le paquet encoding/json/v2 sort du statut expérimental, un paquet uuid fait son entrée dans la bibliothèque standard, crypto/mldsa implémente la signature post-quantique ML-DSA normalisée par la FIPS 204, et le profil goroutineleak, qui repère les goroutines bloquées pour toujours, devient disponible pour tous1. Chacun mérite son propre article, et en aura un.

Tout le code de cet article a été compilé et testé sous Go 1.27.1 : sept tests, go vet et le détecteur de courses compris, y compris les deux erreurs de compilation reproduites telles quelles.

Pendant quatre ans, la réponse à « pourquoi ma méthode ne peut-elle pas être générique ? » était : parce que les interfaces ne le peuvent pas. Go 1.27 a gardé la seconde moitié de la phrase et supprimé la première. C’est un progrès net, à une condition : ne pas oublier que la frontière entre ce qui est générique et ce qui passe par une interface est désormais une décision de conception. Elle se prend une fois, au bon étage, et pas un matin en migrant toutes les fonctions d’aide d’un coup.



  1. Les notes de version de Go 1.27 annoncent les méthodes génériques et précisent : « Note that methods of interfaces may not declare type parameters nor can interface methods be implemented by generic methods. » Elles citent math/rand/v2 comme premier bénéficiaire et listent encoding/json/v2, uuid, crypto/mldsa et le profil goroutineleak. Le billet de sortie est daté du 19 août 2026. ↩︎ ↩︎ ↩︎

  2. Le billet de Mark Freeman sur les méthodes génériques, publié le 26 août 2026, explique pourquoi elles avaient été écartées en Go 1.18 : les méthodes étaient vues comme un moyen d’implémenter des interfaces, et les méthodes d’interface génériques sont difficiles à implémenter efficacement. Il résume la règle restante d’une phrase : « Interface implementation is a property of a (possibly instantiated) type, not of any particular method. » ↩︎ ↩︎

  3. La proposition #77273, ouverte par Robert Griesemer le 22 janvier 2026, précise que les valeurs et expressions de méthode « continue to work as expected: if the method is generic, the resulting function is generic », et que les méthodes génériques échappent à reflect pour la même raison que les fonctions génériques non instanciées : le paquet n’a aucun moyen d’instancier une valeur ou un type générique. Elle ne dit rien de la promotion par incorporation, que j’ai donc vérifiée par un test. ↩︎ ↩︎