Go 1.27 ajoute cinq nouveaux packages à la bibliothèque standard (encoding/json/v2, encoding/json/jsontext, crypto/mldsa, uuid et simd) ainsi que trois changements du langage. Certains vous serviront dès le premier jour, comme les méthodes génériques et un package uuid qui retire une dépendance de la plupart des fichiers go.mod. D'autres feront échouer votre suite de tests avant même que vous ayez remarqué la fonctionnalité : compress/flate produit des octets différents, les littéraux de fonction reçoivent des noms de symboles différents, et une poignée de GODEBUG qui servaient de porte de sortie font désormais échouer le build au lieu de restaurer l'ancien comportement.
Table des matières
- Les méthodes génériques, et la seule chose qu'elles ne savent toujours pas faire
- Un package uuid dans la bibliothèque standard
- Faut-il modifier votre code pour encoding/json/v2 ?
- Signatures post-quantiques avec crypto/mldsa
- Le package expérimental simd
- Les performances gagnées par simple mise à jour
- Détecter les fuites de goroutines et lire les tracebacks avec labels
- Petites améliorations du langage et de la bibliothèque
- Qu'est-ce qui change dans la toolchain Go ?
- Tests et net/http
- Qu'est-ce qui casse lors du passage à Go 1.27 ?
- Questions fréquentes
- Sources
- Pour aller plus loin
Les méthodes génériques, et la seule chose qu'elles ne savent toujours pas faire
Depuis l'arrivée des génériques dans Go 1.18, les paramètres de type ne fonctionnaient qu'à deux endroits : les fonctions de premier niveau et les déclarations de type. Les méthodes en étaient exclues. Si vous vouliez une transformation qui change le type des éléments de votre collection, vous deviez donc l'écrire sous forme de fonction au niveau du package, et les fonctions au niveau du package ne s'enchaînent pas.
Vous avez sans doute déjà écrit quelque chose de ce genre. Un pipeline de métriques qui transforme des relevés de capteurs en libellés d'affichage a besoin de deux transformations, et chacune doit envelopper l'appel précédent :
example.gogotype Metric struct { Sensor string Celsius int } type List[T any] []T // Package-level, because a method couldn't declare U. func Map[T, U any](l List[T], f func(T) U) List[U] { out := make(List[U], len(l)) for i, v := range l { out[i] = f(v) } return out } func main() { metrics := List[Metric]{ {Sensor: "cpu0", Celsius: 61}, {Sensor: "cpu1", Celsius: 58}, } temps := Map(metrics, func(m Metric) int { return m.Celsius }) labels := Map(temps, func(c int) string { return fmt.Sprintf("%dC", c) }) fmt.Println(labels) // [61C 58C] }
Avec Go 1.27, Map devient une vraie méthode qui déclare son propre paramètre de type, indépendamment du récepteur :
example.gogo// U is the method's own type parameter, new in Go 1.27. func (l List[T]) Map[U any](f func(T) U) List[U] { out := make(List[U], len(l)) for i, v := range l { out[i] = f(v) } return out } func main() { metrics := List[Metric]{ {Sensor: "cpu0", Celsius: 61}, {Sensor: "cpu1", Celsius: 58}, } labels := metrics. Map(func(m Metric) int { return m.Celsius }). Map(func(c int) string { return fmt.Sprintf("%dC", c) }) fmt.Println(labels) // [61C 58C] }
L'enchaînement est l'amélioration évidente. Le nommage l'est moins : MapSlice, MapSet et MapBox se réduisent à un seul Map par type, car le récepteur indique déjà de quel type il s'agit. Le récepteur aide aussi le compilateur : List[Metric] fixe T, il ne reste donc que le type de sortie à inférer. Enfin, les méthodes apparaissent quand vous tapez un point dans votre éditeur. Un helper au niveau du package ne vous sert que si vous savez déjà qu'il existe.
La bibliothèque standard utilise elle-même la fonctionnalité. math/rand/v2 déclare désormais (*Rand) N[Int intType](Int) Int comme méthode, à côté de la fonction N au niveau du package, ce que les anciennes règles n'autorisaient pas.
Il y a une limite, et elle passe facilement inaperçue. D'après les release notes :
Ceci ne compile donc pas :
example.gogotype Transformer interface { // Compile error: interface methods can't declare type parameters. Map[U any](f func(Metric) U) any }
Le dispatch des interfaces a lieu à l'exécution, le compilateur ne peut donc pas savoir quelles instanciations d'une méthode générique générer pour un appel résolu dynamiquement. Si votre API repose sur une interface, les transformations ont toujours besoin de l'ancienne fonction au niveau du package. L'intérêt des méthodes génériques pour votre codebase se joue surtout là, alors vérifiez vos interfaces avant de planifier un refactoring. Si vous voulez vous entraîner, le cours Go Generics Masterclass couvre les contraintes et l'inférence sous forme d'exercices dans le navigateur.
Verdict : à adopter dès maintenant, pour les types concrets. Laissez de côté les API construites autour d'interfaces.
Un package uuid dans la bibliothèque standard
Presque tous les services Go qui dialoguent avec une base de données importent github.com/google/uuid. Go 1.27 ajoute un package de la bibliothèque standard dont le chemin d'import est "uuid", à la suite de la proposition #62026.
UUID est défini comme [16]byte, ce qui signifie que les valeurs sont comparables avec == et utilisables directement comme clés de map. Trois générateurs sont fournis :
example.gogopackage main import ( "fmt" "uuid" ) func main() { fmt.Println(uuid.New()) // reach for this when you don't care how it's made fmt.Println(uuid.NewV4()) // 128 bits, 122 of them random fmt.Println(uuid.NewV7()) // 48-bit timestamp first, so values sort by creation time requestID, err := uuid.Parse("f81d4fae-7dec-11d0-a765-00a0c91e6bf6") if err != nil { return } fmt.Println(requestID, requestID == uuid.Nil()) // false, it parsed fine }
New est le générateur par défaut et renvoie actuellement un V4. NewV4 est entièrement aléatoire, personne ne peut donc deviner le suivant. C'est ce qu'il vous faut pour un identifiant de requête public. NewV7 place un horodatage de 48 bits en tête, si bien que les valeurs récentes se trient après les plus anciennes. Elles font donc de bonnes clés primaires, car les insertions arrivent vers la fin de l'index au lieu de s'éparpiller partout.
Notez que Nil() et Max() sont des fonctions : la comparaison s'écrit donc id == uuid.Nil(), avec les parenthèses. Nil() sert de sentinelle « pas encore défini ». Max() est la valeur dont tous les bits valent 1, utile comme borne supérieure quand vous parcourez une plage de clés V7 ordonnées dans le temps.
Verdict : à adopter dès maintenant pour le nouveau code. Migrer un service existant se résume à un rechercher-remplacer, mais consultez la FAQ avant de supprimer la dépendance.
Faut-il modifier votre code pour encoding/json/v2 ?
Non. C'était tout l'objectif de la conception. encoding/json s'appuie désormais sur l'implémentation v2, et les release notes le disent explicitement : « le comportement du marshaling et de l'unmarshaling est préservé, mais le texte exact des messages d'erreur peut différer. » Vous profitez de la vitesse du nouveau moteur sans modifier une ligne de code. Selon les release notes, « les performances de Marshal sont globalement au niveau de l'implémentation précédente, tandis que celles de l'unmarshaling sont nettement meilleures. »
Vous n'optez pour le nouveau comportement qu'en important encoding/json/v2 nommément. Ce package a des valeurs par défaut plus strictes, choisies pour correspondre à ce qu'attendent les autres implémentations JSON : il rejette l'UTF-8 invalide dans les chaînes JSON, rejette les noms en double au sein d'un objet JSON, compare les noms de champs en respectant la casse, sérialise une slice nil en [] plutôt qu'en null, et sérialise les maps dans un ordre non déterministe là où v1 garantissait un ordre déterministe. Le changement sur les slices nil correspond à la distinction entre nil et vide présentée dans var vs make en Go, et avec v2, vous la voyez dans la sortie.
example.gogoimport "encoding/json/v2" type Event struct { ID string `json:"id"` Action string `json:"action"` } // v1 would match the key "ID" against the tag `json:"id"`. v2 won't, // and it skips the mismatched key silently rather than erroring. var e Event json.Unmarshal([]byte(`{"ID":"evt_01H","action":"checkout"}`), &e) fmt.Println(e.ID, e.Action) // "" checkout // v2 marshals maps in a non-deterministic order. Ask for a stable one // explicitly when you hash or snapshot the output. inventory := map[string]int{"widget": 12, "gadget": 3, "gizmo": 7} b, _ := json.Marshal(inventory, json.Deterministic(true))
Pour retrouver un comportement de v1 sous v2, vous ne changez pas d'import. Vous passez une option pour ce comportement précis : Deterministic(true) pour un ordre stable des maps, MatchCaseInsensitiveNames(true) pour une correspondance souple des champs, FormatNilSliceAsNull(true) pour null au lieu de []. Le package v1 a reçu les mêmes options, vous pouvez donc adopter la sémantique v2 un comportement à la fois, sans migration complète. La liste complète se trouve dans la section Migrating to v2 de la documentation du package v1.
Le troisième package, encoding/json/jsontext, gère la syntaxe de plus bas niveau. Il expose le JSON comme une suite de tokens et de valeurs, avec une machine à états qui garantit la validité de la sortie. Les codecs en streaming s'appuient dessus. La plupart du code applicatif ne l'importe jamais directement.
Pour choisir lequel importer :
flowchart TD A["Which JSON import?"] --> B{"Upgrading existing code?"} B -- Yes --> C["encoding/json<br/>v2 engine, v1 behavior"] B -- No --> D{"Want strict defaults<br/>and faster decoding?"} D -- No --> C D -- Yes --> E["encoding/json/v2"] E --> F{"Need raw tokens<br/>or streaming syntax?"} F -- Yes --> G["encoding/json/jsontext"] F -- No --> H["You're done"]
Arbre de décision pour choisir entre encoding/json, encoding/json/v2 et encoding/json/jsontext dans Go 1.27.
Verdict : adoptez le moteur par défaut dès maintenant (c'est déjà fait), et v2 nommément plus tard, une fois que vous aurez lu les notes de migration concernant vos struct tags. Le cours Data Formats couvre les règles de tags qui déterminent à quel point cela vous concerne.
Signatures post-quantiques avec crypto/mldsa
RSA et ECDSA sont sûrs aujourd'hui parce qu'aucun ordinateur ne sait factoriser de grands nombres ou calculer des logarithmes discrets assez vite. Un ordinateur quantique suffisamment puissant le pourrait. crypto/mldsa implémente ML-DSA, défini par FIPS 204, avec trois jeux de paramètres, 44, 65 et 87, à la suite de la proposition #77626.
D'autres packages le prennent déjà en charge. crypto/x509 analyse et vérifie les clés et signatures ML-DSA, et crypto/tls accepte les trois jeux de paramètres lors d'un handshake TLS 1.3. MLKEM1024 rejoint les échanges de clés pris en charge pour un accord de clé résistant au quantique, activé en l'ajoutant à Config.CurvePreferences.
Une signature et une vérification complètes pour une release de firmware :
example.gogosk, err := mldsa.GenerateKey(mldsa.MLDSA65()) if err != nil { return err } firmware, err := os.ReadFile("firmware-v2.1.0.bin") if err != nil { return err } // Context is a domain-separation label. Sign and verify must pass the same one. opts := &mldsa.Options{Context: "acme/firmware-release"} sig, err := sk.Sign(nil, firmware, opts) if err != nil { return err } fmt.Println(mldsa.Verify(sk.PublicKey(), firmware, sig, opts) == nil) // true fmt.Println(mldsa.MLDSA65().SignatureSize()) // 3309 firmware[0] ^= 1 // flip one bit fmt.Println(mldsa.Verify(sk.PublicKey(), firmware, sig, opts) == nil) // false
Verify renvoie nil quand la signature est valide, la comparaison avec nil vous donne donc un booléen. L'inconvénient, c'est la taille, et l'écart est important :

Source : tailles de signature selon FIPS 204 (ML-DSA) et les spécifications des algorithmes respectifs.
Une signature ML-DSA-65 fait environ 46 fois la taille d'une signature Ed25519, elle ne vaut donc la peine que là où la signature doit durer. Le firmware et les artefacts de release auxquels un appareil devra encore faire confiance dans dix ans valent bien 3 KB. Il en va de même pour les racines de certificats censées survivre à la cryptographie actuelle, et pour les serveurs TLS dont vous préférez ne pas avoir à changer les clés une fois les ordinateurs quantiques arrivés. Pour des jetons de session à courte durée de vie que vous émettez par milliers chaque seconde, la taille supplémentaire est un surcoût dont vous n'avez pas encore besoin.
Un changement sans rapport dans crypto/x509 touche le même domaine. SystemCertPool respecte désormais SSL_CERT_FILE et SSL_CERT_DIR sous Windows et Darwin, et plus seulement sous Linux. Quand ces variables sont définies, Go charge les racines depuis le disque et utilise son propre vérificateur au lieu des API de la plateforme. Définissez GODEBUG=x509sslcertoverrideplatform=0 pour conserver l'ancien comportement.
Verdict : à adopter pour les signatures à longue durée de vie, à ignorer pour tout le reste.
Le package expérimental simd
Une instruction CPU classique travaille sur une seule valeur. Une instruction SIMD travaille sur tout un vecteur de valeurs en une seule étape, si bien que le même résultat demande beaucoup moins d'instructions. C'est utile quand vous appliquez les mêmes calculs à une grande slice d'échantillons audio ou de pixels d'image, ou quand vous calculez un produit scalaire. Le code qui se contente surtout de déplacer des structs et des chaînes n'en profitera pas.
Chaque architecture expose SIMD différemment. Le nouveau package simd est portable et ne présuppose aucune taille de vecteur. Il est disponible sur toutes les architectures et utilise les instructions matérielles là où elles existent. Activez-le avec GOEXPERIMENT=simd au moment du build. Il est expérimental, l'API peut donc changer.
Mixer deux pistes audio, plusieurs échantillons par étape :
example.gogo//go:build goexperiment.simd // out[i] = trackA[i] + trackB[i], several samples at a time. out := make([]float32, len(trackA)) lanes := simd.LoadFloat32s(trackA).Len() // how many float32s fit in one vector for i := 0; i+lanes <= len(trackA); i += lanes { va := simd.LoadFloat32s(trackA[i:]) vb := simd.LoadFloat32s(trackB[i:]) va.Add(vb).Store(out[i:]) } // A plain scalar loop handles the leftover tail.
Len indique combien d'éléments tiennent dans un vecteur sur la machine où le code s'exécute, la boucle n'a donc jamais besoin de coder en dur une largeur de vecteur. Chaque étape charge autant d'échantillons depuis les deux pistes, les additionne élément par élément en une seule opération, puis stocke le résultat.
Verdict : à ignorer pour l'instant, sauf si vous profilez déjà une boucle numérique critique. L'API est derrière un GOEXPERIMENT et susceptible de changer.
Les performances gagnées par simple mise à jour
Certaines accélérations ne demandent rien d'autre qu'une recompilation. La plus importante est l'allocation spécialisée par taille, que les release notes décrivent ainsi :

Source : release notes de Go 1.27, allocation mémoire plus rapide.
Ces deux chiffres mesurent des choses différentes. Les 30 % correspondent au coût d'un seul petit appel d'allocation. Le ~1 % est ce que constate un service entier. Si vous rencontrez une régression, GOEXPERIMENT=nosizespecializedmalloc permet de désactiver la fonctionnalité, et cette porte de sortie devrait disparaître dans Go 1.28.
Trois optimisations du compilateur sont aussi activées par défaut. Une passe d'analyse de flot de données sur les bits connus suit quels bits d'une valeur valent de façon prouvable 0 ou 1, et élimine la redondance qui en résulte. Le déplacement de code invariant de boucle sort du corps de la boucle les calculs dont le résultat ne change jamais, qui s'exécutent alors une seule fois au lieu de s'exécuter à chaque itération. Enfin, les instructions switch se compilent désormais en tables de correspondance quand les cas le permettent, y compris avec fallthrough, et sautent directement au cas correspondant au lieu de tester chacun d'eux.
compress/flate devient aussi plus rapide, avec une réserve : la sortie encodée exacte peut différer de celle de Go 1.26. DEFLATE se trouve sous archive/zip, compress/gzip, compress/zlib et image/png, donc les tests golden à l'octet près sur n'importe lequel d'entre eux peuvent échouer. La sortie reste correcte, il suffit de régénérer les fixtures.
Verdict : à adopter dès maintenant. Vous en bénéficiez de toute façon en mettant à jour.
Détecter les fuites de goroutines et lire les tracebacks avec labels
Le profil goroutineleak est passé du statut d'expérimentation à la disponibilité générale, et le GOEXPERIMENT goroutineleakprofile est supprimé. Il est exposé via runtime/pprof et via l'endpoint /debug/pprof/goroutineleak de net/http/pprof.
Une goroutine qui fuit est une goroutine bloquée sur une primitive de concurrence qui ne pourra jamais se débloquer. Le runtime les détecte grâce au garbage collector : si la goroutine G est bloquée sur la primitive P, et que P n'est accessible depuis aucune goroutine exécutable ni depuis quoi que ce soit que celles-ci pourraient débloquer, alors G ne pourra jamais se réveiller.
example.gogofunc startJob() { result := make(chan int) // unbuffered: the send waits for a receiver go func() { result <- expensiveWork() // blocks forever, nobody receives }() // returns without receiving, so result becomes unreachable } func main() { startJob() runtime.GC() // the detector scans during a GC cycle pprof.Lookup("goroutineleak").WriteTo(os.Stdout, 1) // goroutineleak profile: total 1 // ... main.startJob.func1 ... main.go:14 }
Cette approche a un angle mort. Le runtime peut manquer des fuites lorsque la primitive bloquante est accessible via une variable globale ou via les variables locales d'une goroutine exécutable. Un channel stocké dans un registre au niveau du package compte comme accessible, le profil ne le signalera donc pas. Le cours Concurrency Fundamentals passe en revue les schémas de fuite qui produisent ces situations, et la manière de les corriger avec context.
La seconde amélioration du débogage ne demande aucun code. Pour les modules dont la directive go est 1.27 ou ultérieure, les tracebacks incluent désormais les labels de goroutine de runtime/pprof dans la ligne d'en-tête. Les labels que vous définissez déjà pour le profilage apparaissent dans les crash dumps, les traces SIGQUIT et la sortie de runtime.Stack. Quand deux goroutines ont des piles identiques, le label est souvent le seul moyen de les distinguer :
example.gogopprof.Do(ctx, pprof.Labels("request", id), func(ctx context.Context) { work() }) // goroutine 34 [running]: // labels: {"request":"req-42"} // main.handle.func1 ...
Définissez GODEBUG=tracebacklabels=0 pour désactiver ce comportement si vos labels contiennent des informations que vous ne voulez pas voir dans un crash dump.
Verdict : à adopter dès maintenant. Ni l'un ni l'autre ne vous coûte quoi que ce soit.
Petites améliorations du langage et de la bibliothèque
Sélecteurs de champs dans les littéraux de struct
Une clé dans un littéral de struct peut désormais être n'importe quel sélecteur de champ valide pour le type, et plus seulement un nom de champ de premier niveau (proposition #9859). Si vous embarquez un modèle partagé, vous n'avez plus besoin d'écrire le littéral imbriqué en entier :
example.gogotype Model struct { ID int64 CreatedAt time.Time } type Post struct { Model Author string Likes int } p := Post{Model: Model{ID: 42}, Author: "Patrik"} // before p := Post{ID: 42, Author: "Patrik"} // Go 1.27
Inférence généralisée du type des fonctions
L'inférence du type des fonctions s'applique désormais dans tous les contextes où une fonction générique est affectée ou convertie vers un type de fonction correspondant (proposition #77245). Auparavant, elle ne fonctionnait que dans une déclaration de variable typée :
example.gogofunc ascending[T cmp.Ordered](a, b T) int { return cmp.Compare(a, b) } func descending[T cmp.Ordered](a, b T) int { return cmp.Compare(b, a) } orders := []func(int, int) int{ascending[int], descending[int]} // before orders := []func(int, int) int{ascending, descending} // Go 1.27
Le type des éléments de la slice est func(int, int) int, Go infère donc T comme int. Cela fonctionne désormais aussi pour les conversions et pour le passage d'une fonction générique non instanciée en argument.
strings.CutLast et bytes.CutLast
Cut découpe sur le premier séparateur. CutLast découpe sur le dernier, et renvoie la partie avant, la partie après et un indicateur précisant si le séparateur a été trouvé (proposition #71151) :
example.gogodir, file, found := strings.CutLast("internal/store/user.go", "/") fmt.Println(dir, file, found) // internal/store user.go true
Auparavant, il fallait appeler LastIndex, vérifier -1, puis découper deux fois à la main.
Rand.N, maphash.Hasher et big.Int.Divide
math/rand/v2 reçoit Rand.N sous forme de méthode générique (proposition #77853). Vous pouvez donc tirer une valeur aléatoire bornée depuis une source que vous avez initialisée avec une graine, plutôt que depuis la source globale, dont la graine ne peut pas être fixée. Un test en échec se rejoue alors avec les mêmes entrées.
hash/maphash gagne une interface générique Hasher[T] pour les structures basées sur le hachage, comme les maps personnalisées et les filtres de Bloom (proposition #70471). Elle associe une fonction de hachage à un test d'égalité, et des valeurs égales doivent produire le même hash. ComparableHasher[T] est l'implémentation prête à l'emploi pour les types comparables, qui compare avec ==. go/types l'utilise déjà et fournit un Hasher qui respecte la relation Identical.
math/big.Int reçoit une méthode Divide qui calcule le quotient et le reste en une fois, selon le mode d'arrondi de votre choix : Trunc, Floor, Round ou Ceil. Quo et Mod tronquent toujours vers zéro, le code financier et numérique qui a besoin d'un autre arrondi dispose donc désormais d'une option intégrée.
Unicode 17, database/sql et le reste
Le package unicode et tout ce qui repose dessus sont passés directement d'Unicode 15 à Unicode 17, les caractères ajoutés dans les deux versions intermédiaires sont donc désormais correctement classés. database/sql a gagné ConvertAssign, qui permet aux drivers de réutiliser les conversions de type effectuées par Rows.Scan au lieu d'écrire les leurs.
Le reste est mineur :
cryptoajoute la valeur de hachageMLDSAMu, un mécanisme de signalisation pour la signature ML-DSA avec mu externe.crypto/ecdsavérifie désormais que la longueur du hash est correcte dansPrivateKey.Signlorsque vous passez desSignerOptsnon nil.crypto/x509exposeRawSignatureAlgorithmsurCertificate,CertificateRequestetRevocationList, ce qui vous donne l'AlgorithmIdentifier encodé en DER même quandSignatureAlgorithmvautUnknownSignatureAlgorithm. L'analyse verspkix.Nameaccepte aussi un éventail plus large de types de valeurs, les types inconnus atterrissant dansasn1.RawValue.crypto/tlsajouteConnectionState.LocalCertificate, la chaîne que vous avez présentée au pair, ainsi queQUICConfig.ClientHelloInfoConn.Config.Randest déprécié au profit detesting/cryptotest.SetGlobalRandompour les tests déterministes.netfait en sorte que les méthodes de lecture deUnixConnrenvoient directementio.EOFau lieu de l'envelopper dans unnet.OpError.runtime/secretpropage le mode secret aux goroutines créées à l'intérieur.go/constantajouteStringLen,go/scannerajouteScanner.End, etgo/tokendoteFiled'une méthodeString.- Côté portages, le portage PowerPC 64 bits big-endian sous Linux passe à l'ABI système ELFv2, ce qui y débloque cgo, les exécutables indépendants de la position et l'édition de liens externe, et nécessite un noyau Linux 3.13 ou ultérieur. Sous Plan 9,
syscall.Errnoest désormais défini et implémenteerror, le code portable qui y fait référence se compile donc sans contraintes de build. Le linker accepte aussi-macoset-macsdkpour définir les versions écrites dans la commande de chargement macOSLC_BUILD_VERSION.
Qu'est-ce qui change dans la toolchain Go ?
go fix a gagné quatre modernizers. embedlit simplifie les références aux champs embarqués dans les littéraux composites, ce que la nouvelle règle sur les littéraux de struct rend possible. atomictypes remplace les types de base dans les appels à sync/atomic par des types atomiques, slicesbackward réécrit les boucles à rebours avec slices.Backward, et unsafefuncs remplace l'arithmétique de pointeurs unsafe par des appels de fonction. Deux changements d'entretien les accompagnent : fmtappendf a été supprimé pour des raisons de style, et waitgroup a été renommé en waitgroupgo. Lancez-le une fois après avoir relevé la directive go :
example.bashbashgo fix -diff ./... # preview go fix ./... # apply
Pour le catalogue complet de ce que go fix réécrit, consultez tous les modernizers de go fix 1.26.
go test exécute désormais par défaut la vérification vet stdversion. Elle signale les symboles de la bibliothèque standard plus récents que la version de Go en vigueur pour le fichier, telle que la fixent la directive de votre go.mod et vos build tags. Si votre module déclare go 1.25 et que quelqu'un utilise strings.CutLast, l'exécution des tests le détecte avant qu'un utilisateur en 1.25 ne tombe dessus. go test -json annote aussi les lignes "Action":"output" avec un champ optionnel "OutputType", qui vaut actuellement error, error-continue ou frame, ce qui est pratique si vous analysez la sortie des tests en CI.
go doc accepte désormais la syntaxe package@version : go doc rsc.io/[email protected] affiche la documentation d'une release précise sans avoir à la récupérer localement. Un nouveau flag -ex liste les exemples exécutables d'un package, et en nommer un affiche son code source.
go mod tidy impose une organisation en deux blocs pour les modules en go 1.27 ou ultérieur, en fusionnant les blocs require dispersés en un bloc direct et un bloc indirect. Les blocs de commentaires associés aux dépendances sont conservés, et un commentaire qui couvre un ensemble mixte est déplacé vers le bloc direct.
go tool trace -http=:6060 n'écoute plus que sur localhost, comme go tool pprof. Passez donc une adresse complète comme -http=0.0.0.0:6060 si vous devez y accéder depuis une autre machine. Les outils compile, link, asm, cgo, cover et pack acceptent aussi les fichiers de réponse (@file) dans un format compatible GCC, ce qui aide les systèmes de build qui dépassent les limites de longueur de ligne de commande.
Tests et net/http
httptest.NewTestServer crée un Server sur un faux réseau en mémoire, conçu pour être utilisé avec testing/synctest, sans aucun vrai port TCP en jeu. Son complément est synctest.Sleep, qui effectue time.Sleep et synctest.Wait en un seul appel : l'horloge simulée avance, puis les goroutines se stabilisent. Avec les deux, les tests HTTP ne dépendent plus du timing. Le cours Professional Go Testing couvre le modèle synctest sur lequel ils reposent.
example.gogosrv := httptest.NewTestServer(t, handler) // in-memory, cleanup auto-registered
Côté net/http, le serveur HTTP/2 accepte désormais les signaux de priorité des clients définis par la RFC 9218 et sert en premier les streams de plus haute priorité. Définissez Server.DisableClientPriority = true pour retrouver l'ancien comportement en round-robin.
Le changement que vous avez le plus de chances de remarquer concerne HTTP/1 : fermer un Response.Body partiellement lu draine désormais le contenu restant, jusqu'à une limite prudente, afin que la connexion puisse être réutilisée. Pour la plupart des programmes, cela ne change rien ou apporte un léger gain de vitesse. Si vous fermez tôt pour interrompre un gros téléchargement, Transport.DisableKeepAlives = true désactive ce comportement.
Server.MaxHeaderValueCount limite le nombre de valeurs qu'un même en-tête peut porter, ce qui protège contre les requêtes qui inondent un en-tête. Transport et Server peuvent négocier TLS ALPN sur un net.Conn que vous fournissez vous-même, à condition qu'il implémente ConnectionState() tls.ConnectionState, ce qui permet d'utiliser HTTP/2 sur des connexions tunnelisées ou passant par un proxy. Enfin, net/url a gagné URL.Clone et Values.Clone pour les copies profondes. Le cours HTTP and Networking couvre les réglages de serveur et de transport sur lesquels ils reposent.
Verdict : à adopter dès maintenant. Si votre suite de tests HTTP est instable, httptest combiné à synctest justifie à lui seul la mise à jour.
Qu'est-ce qui casse lors du passage à Go 1.27 ?
Les release notes répartissent les changements cassants sur huit sections. Ce tableau les regroupe, classés selon la probabilité que vous les rencontriez.
| Ce qui change | Qui est touché | Que faire |
|---|---|---|
compress/flate produit des octets différents | Les tests golden à l'octet près sur une sortie gzip, zlib, zip ou png | Régénérez les fixtures. La compression est correcte, simplement différente. |
| Noms plus simples pour les littéraux de fonction (closures) | Les tests qui vérifient des noms de symboles, et le code qui compare l'égalité des pointeurs de code de fonctions | Ne dépendez plus des noms des littéraux. La comparaison de pointeurs de fonctions était déjà documentée comme peu fiable. |
GODEBUG asynctimerchan et gotypesalias supprimés définitivement | Tout ce qui les fige à l'ancienne valeur dans go.mod ou dans une ligne //go:debug | Lancez grep -r asynctimerchan . avant la mise à jour. Définir la valeur par défaut finale compile toujours, définir l'ancienne valeur échoue. |
Cinq GODEBUG TLS/x509 supprimés : tlsunsafeekm, tlsrsakex, tls3des, tls10server, x509keypairleaf | Les services encore figés sur un comportement TLS historique | Même règle que ci-dessus : la commande go accepte la valeur par défaut finale et rejette l'ancienne valeur, vous les repérez donc au moment du build. |
| macOS 13 Ventura est la version minimale | Les runners CI et les machines de développement sous un macOS plus ancien | Mettez à jour l'image du runner. Annoncé dans les notes de Go 1.26. |
Prise en charge de bzr supprimée de la commande go | Les modules hébergés sur des serveurs Bazaar | Créez un miroir de la dépendance, ou vendorez-la. |
La nouvelle directive //go:linknamestd marque les linknames réservés à la std, le linker vérifie désormais l'accès par linkname aux symboles assembleur, et les descripteurs de type ont été déplacés dans une section .go.type | Les packages qui accèdent au runtime via un //go:linkname non autorisé, les vôtres ou ceux d'une dépendance | Mettez à jour la dépendance. Ces erreurs surviennent au build, et bruyamment. Rien de tout cela ne figure dans les release notes, c'est donc le build qui vous l'apprend. |
Response.Body.Close en HTTP/1 draine le contenu non lu | Le code qui ferme tôt pour interrompre un gros téléchargement | Définissez Transport.DisableKeepAlives = true pour ces clients. |
| Le texte des messages d'erreur de json/v2 diffère | Les tests qui vérifient des chaînes d'erreur JSON exactes | Vérifiez le type d'erreur ou une sous-chaîne plutôt que le message complet. |
La dernière ligne est la plus difficile à repérer. Le comportement est préservé, mais pas le texte, si bien qu'une suite de tests qui compare les erreurs d'unmarshaling sous forme de chaînes échoue sans cause évidente. Isolez le problème avec deux exécutions sur une branche où go.mod est déjà passé à go 1.27 :
example.bashbashgo test ./... > default.txt 2>&1 GOEXPERIMENT=nojsonv2 go test ./... > nojsonv2.txt 2>&1 diff default.txt nojsonv2.txt
Tout ce qui apparaît dans le diff vient du changement JSON. Tout ce qui échoue dans les deux exécutions relève d'une autre ligne du tableau. GOEXPERIMENT=nojsonv2 devrait être supprimé dans une future release : servez-vous-en pour diagnostiquer, puis corrigez les tests.
Questions fréquentes
Dois-je réécrire mon code JSON pour Go 1.27 ?
Non. Le package encoding/json est désormais implémenté au-dessus de v2, mais le comportement du marshaling et de l'unmarshaling est préservé et l'API v1 reste prise en charge. La seule différence observable est que le texte exact des messages d'erreur peut changer. Vous n'optez pour les valeurs par défaut plus strictes de v2 qu'en important explicitement encoding/json/v2.
Une méthode générique peut-elle satisfaire une interface ?
Non. Les release notes indiquent que les méthodes d'interface ne peuvent pas déclarer de paramètres de type, et que les méthodes d'interface ne peuvent pas être implémentées par des méthodes génériques. Le dispatch des interfaces se résout à l'exécution, le compilateur ne peut donc pas savoir quelles instanciations générer pour un appel résolu dynamiquement. Si votre API repose sur des interfaces, continuez à utiliser des fonctions génériques au niveau du package.
Faut-il abandonner github.com/google/uuid au profit du package de la bibliothèque standard ?
Pour du nouveau code, oui. Pour du code existant, vérifiez d'abord deux choses. Le UUID de la bibliothèque standard est un [16]byte doté de son propre ensemble de méthodes, donc tout code qui utilise les méthodes supplémentaires du type tiers doit être revu. Et toute dépendance qui expose le uuid.UUID tiers dans son API maintient de toute façon ce module dans votre graphe.
crypto/mldsa est-il prêt pour la production ?
C'est un package stable de la bibliothèque standard qui implémente FIPS 204, intégré à crypto/x509 et crypto/tls. La contrainte pratique est la taille des signatures, pas la maturité : les signatures ML-DSA-65 font 3 309 octets, contre 64 pour Ed25519. Utilisez-le là où la signature doit survivre à la cryptographie actuelle, et laissez-le de côté pour les jetons à courte durée de vie émis en grand volume.
Comment repérer au plus vite ce que Go 1.27 casse dans mon code ?
Passez go.mod à go 1.27 sur une branche, puis cherchez avec grep asynctimerchan, gotypesalias et les cinq réglages TLS supprimés, car ceux-ci font échouer le build directement. Lancez ensuite votre suite deux fois, une fois normalement et une fois avec GOEXPERIMENT=nojsonv2, et comparez les échecs avec diff. Cela distingue les écarts de texte des erreurs JSON des échecs liés aux fichiers golden et aux noms de symboles.
Sources
Références principales citées dans cet article (dernière vérification le 23 août 2026) :
- Go 1.27 Release Notes, consulté le 2026-08-23
- Go 1.27 interactive tour, VictoriaMetrics, consulté le 2026-08-23
- Documentation du package
uuid, consultée le 2026-08-23 - Documentation du package
encoding/json/v2, consultée le 2026-08-23 encoding/json: Migrating to v2, la liste complète des différences de comportement, consulté le 2026-08-23- Documentation du package
crypto/mldsa, consultée le 2026-08-23 - Documentation du package
simd, consultée le 2026-08-23 - Documentation du package
net/http/httptest, consultée le 2026-08-23 - NIST FIPS 204 : Module-Lattice-Based Digital Signature Standard, consulté le 2026-08-23
- Documentation de l'analyseur modernize de
x/tools, consultée le 2026-08-23 - Notes de version d'Unicode 17.0.0, consultées le 2026-08-23
Pour aller plus loin
Les méthodes génériques sont plus faciles à bien utiliser une fois que les contraintes et l'inférence vous sont devenues naturelles, et ce sont justement les parties que l'on a tendance à survoler en découvrant les génériques. Le cours Go Generics Masterclass aborde les contraintes, les ensembles de types et l'inférence sous forme d'exercices dans le navigateur qui tournent sur la toolchain actuelle, pour que vous puissiez essayer la syntaxe des méthodes de Go 1.27 dans du vrai code.
Vous débutez en Go ? Commencez par le parcours Go Fundamentals et revenez ensuite pour les release notes. Et si vous avez fait l'impasse sur la release de l'an dernier, Nouveautés de Go 1.26 couvre errors.AsType, le garbage collector Green Tea et new(expr).
