Go 1.27, of Hoe ik leerde stoppen met piekeren en van Generic Methods leerde houden

Go 1.27 verschijnt in augustus 2026, en voor de verandering is de taal zelf veranderd. Niet op de manier van "we hebben een nieuwe functie toegevoegd aan slices", maar echt veranderd. Generieke methoden zijn toegevoegd. Een elf jaar oud issue is gesloten. encoding/json werd stilletjes vervangen door een nieuwe engine terwijl het vliegtuig in de lucht was.
Elk onderstaand voorbeeld is uitgevoerd op go1.27rc3 (darwin/arm64). Elk output-blok bevat echte output, gekopieerd en geplakt, inclusief de foutmeldingen. Als iets er vreemd uitziet, is dat omdat de compiler dat daadwerkelijk zo aangaf.
1go install golang.org/dl/go1.27rc3@latest
2go1.27rc3 download
1. De taal is veranderd (ja, echt)
1.1. Generieke methoden
Sinds Go 1.18 hebben we generics, en sinds Go 1.18 hebben we "The Conversation". Het gaat als volgt:
"Laat me gewoon een
Map-methode schrijven op mijn slice-type—"
method must have no type parameters"...vooruit dan maar. Het wordt een functie op package-niveau."
Methoden konden voorheen alleen typeparameters gebruiken die door de receiver waren gedeclareerd. Je eigen methode kon geen nieuwe introduceren. Dus elke generieke transformatie werd verbannen naar de package-scope, waar het naast zeventien andere vrij zwevende hulpprogramma's stond met namen als MapSlice.
Go 1.27 lost dit op:
1type List[E any] []E
2
3// F is een typeparameter gedeclareerd door de methode zelf
4func (l List[E]) Apply[F any](f func(E) F) List[F] {
5 r := make(List[F], len(l))
6 for i, x := range l {
7 r[i] = f(x)
8 }
9 return r
10}
11
12func main() {
13 l := List[int]{1, 2, 3}
14 fmt.Println(l.Apply(func(i int) string { return fmt.Sprint(i * 10) }))
15 // [10 20 30]
16}
De receiver hoeft niet eens generiek te zijn. Een eenvoudige struct kan een generieke methode hebben:
1type Bag struct{ items []any }
2
3func (b *Bag) Add[T any](v T) { b.items = append(b.items, v) }
Voordat u vanmiddag uw hele codebase gaat refactoren: er zijn twee beperkingen, en die zijn belangrijker dan ze lijken:
- Interface-methoden kunnen geen typeparameters declareren.
- Generieke methoden kunnen geen interface-methoden implementeren.
Die tweede is degene die u zal raken:
1type Adder interface{ Add(int) int }
2
3type G struct{}
4
5func (G) Add[T any](v T) T { return v }
6
7var _ Adder = G{}
1cannot use G{} (value of struct type G) as Adder value in variable declaration:
2 G does not implement Adder (wrong type for method Add)
3 have Add[T any](T) T
4 want Add(int) int
Dus generieke methoden en dynamic dispatch gaan niet samen. Dit is niet omdat het Go-team gierig is — het is omdat een generieke methode oneindig veel instantiaties heeft, en een method-table van een interface moet eindig zijn en bekend bij compile-time. U kunt geen oneindig object in een eindige tabel plaatsen. Het universum zei nee.
Praktisch gelezen: generieke methoden zijn voor concrete types met utility-achtige API's. Alles wat achter een interface moet schuilen, heeft nog steeds de oude aanpak nodig.
De standaardbibliotheek heeft er al gebruik van gemaakt. math/rand/v2's Rand heeft een generieke methode gekregen:
1func (r *Rand) N[Int intType](n Int) Int
1r := rand.New(rand.NewPCG(1, 2))
2fmt.Println(r.N(int32(100))) // 76
3fmt.Println(r.N(time.Second)) // 616.436223ms
Voorheen was alleen de rand.N op package-niveau generiek, wat betekende dat de globale source moest worden gebruikt. Nu krijgt uw eigen geseedde *Rand hetzelfde gemak. Een kleinigheidje. Wel fijn.
1.2. Struct literal field selectors, of: Issue #9859 mag eindelijk rusten
Embedding geeft u u.ID. Embedding geeft u niet User{ID: 1}. In plaats daarvan geeft het User{Base: Base{ID: 1}}, wat de manier van Go is om te vragen of u dat echt zo bedoelde.
Issue #9859 werd in 2015 ingediend. Het is nu gesloten. Ergens krijgt een gopher die inmiddels twee keer van carrière is gewisseld een GitHub-notificatie.
1type Object struct{ name, color string }
2type Point3D struct {
3 Object
4 x, y, z float64
5}
6type Line struct {
7 Object
8 p, q Point3D
9}
10
11// Go 1.27: gebruik het gepromote veld direct
12line := Line{name: "diagonal", q: Point3D{y: -4, z: 12.3}}
name is geen veld van Line. Het is een veld van de embedded Object. Voorheen schreef u Line{Object: Object{name: "diagonal"}, ...}.
Nu de kleine lettertjes, die ik heb gecontroleerd door ze daadwerkelijk allemaal te compileren.
(a) De key is nog steeds een gewoon identifier. U kunt geen willekeurig selector-pad schrijven.
1_ = Line{Object.name: "x"} // invalid field name Object.name in struct literal
2_ = Line{p.x: 1} // invalid field name p.x in struct literal
De functionaliteit is dus niet "keys werken nu als field access". Het is specifiek "impliciet gepromote veldnamen zijn toegestaan". Iets beperkter dan de kop suggereert.
(b) U kunt niet tegelijkertijd een embedded veld en iets dat daaruit gepromoot is specificeren.
1obj := Object{"edge", "black"}
2_ = Line{Object: obj, name: "diagonal"}
3// cannot specify promoted field name and enclosing embedded field Object
Redelijk. U gaf twee tegenstrijdige opdrachten over hetzelfde geheugen en de compiler weigerde te gokken.
(c) Pointer embedding is niet uitgenodigd.
1type PtrEmbed struct {
2 *Object
3 z int
4}
5
6_ = PtrEmbed{name: "x"}
7// invalid implicit pointer indirection to reach name
Om een pointer te volgen heeft u een pointer nodig om te volgen, en tijdens de constructie van de literal is die er nog niet. Ook eerlijk.
Goed nieuws: go fix zal uw oude literals voor u herschrijven. Later meer daarover.
1.3. Type-inferentie voor functies is minder willekeurig geworden
Type-inferentie voor generieke functies werkte voorheen op sommige plekken wel en op andere niet, zonder duidelijk principe. Toewijzen aan een variabele? Prima. Dezelfde functie in een struct-veld plaatsen? Compilerfout, schrijf alstublieft double[int] alsof het 2022 is.
Go 1.27 laat inferentie werken in elke context waar het doeltype ondubbelzinnig bekend is:
1func double[T ~int | ~float64](v T) T { return v * 2 }
2
3type S struct{ f func(int) int }
4type A [1]func(int) int
5
6func main() {
7 s := S{f: double} // 1.26: had double[int] nodig
8 a := A{double} // 1.26: had double[int]
9
10 c := make(chan func(int) int, 1)
11 c <- double // 1.26: had double[int]
12
13 var fn func(float64) float64 = double // dit werkte altijd al
14
15 fmt.Println(s.f(21), a[0](5), (<-c)(7), fn(1.5))
16 // 42 10 14 3
17}
Eén double, geïnstantieerd als func(int) int op drie plekken en func(float64) float64 op een vierde, volledig op basis van context. Als u optie-structs of handler-tabellen vol functiewaarden schrijft, zal een hoop [T]-ruis binnenkort uit uw codebase verdwijnen.
2. Runtime: Gratis prestaties en een lek-detector
2.1. Grootte-gespecialiseerde allocatie
De compiler genereert nu aanroepen naar grootte-gespecialiseerde allocatieroutines voor kleine objecten. De release notes claimen tot 30% winst op allocaties onder de 80 bytes, en ongeveer 1% in het algemeen voor programma's die veel alloceren.
Ik geloof release notes niet zonder metingen, dus:
1type small struct{ a, b, c, d int }
2
3var sink any
4
5func BenchmarkAlloc(b *testing.B) {
6 for b.Loop() {
7 sink = &small{}
8 }
9}
10
11func BenchmarkSlice(b *testing.B) {
12 for b.Loop() {
13 sink = make([]byte, 48)
14 }
15}
1# standaard (grootte-gespecialiseerde malloc aan)
2BenchmarkAlloc-12 3000000 5.358 ns/op
3BenchmarkSlice-12 3000000 13.58 ns/op
4
5# GOEXPERIMENT=nosizespecializedmalloc
6BenchmarkAlloc-12 3000000 8.722 ns/op
7BenchmarkSlice-12 3000000 20.81 ns/op
30–35% sneller in een microbenchmark die niets anders doet dan alloceren op Apple Silicon. Oftewel: dit is het best-case scenario, bereikt onder laboratoriumcondities door een benchmark die ontworpen is om het getal er goed uit te laten zien. In een echt programma zullen de GC en daadwerkelijk werk het meeste daarvan tenietdoen. De claim van ~1% is de eerlijke.
De kosten zitten in de binaire grootte. Hello World:
12413202 bytes (standaard)
22361826 bytes (nosizespecializedmalloc)
Ongeveer 50KB, vast, ongeacht uw programma. U kunt zich afmelden met GOEXPERIMENT=nosizespecializedmalloc, maar dat ontsnappingsluik staat gepland voor verwijdering in Go 1.28, dus beschouw het als een workaround voor bug-rapportage in plaats van een levensstijl.
2.2. De Goroutine Leak Profile is nu echt
Experimenteel in Go 1.26, algemeen beschikbaar in 1.27. De goroutineleakprofile GOEXPERIMENT is verdwenen; het werkt gewoon.
1func leak() {
2 ch := make(chan int) // niemand zal ooit sturen. niemand.
3 go func() {
4 <-ch
5 }()
6}
7
8func main() {
9 for range 3 {
10 leak()
11 }
12 runtime.GC()
13 runtime.GC()
14 pprof.Lookup("goroutineleak").WriteTo(os.Stdout, 1)
15}
1goroutineleak profile: total 3
23 @ 0x104de3f18 0x104d7f0b0 0x104d7ec34 0x104e374f4 0x104dea024
3# 0x104e374f3 main.leak.func1+0x23 /tmp/go127/leak/main.go:12
Drie goroutines, permanent vastgelopen, met een bestandsnaam en regelnummer die precies wijzen op waar u het deed.
De truc hierachter is oprecht slim: het hergebruikt de bereikbaarheidsanalyse van de garbage collector. Als goroutine G geblokkeerd is op primitieve P, en P is onbereikbaar vanuit elke runnable goroutine (of alles wat die goroutines zouden kunnen wekken), dan kan niets ooit P meer aanraken, dus zal G nooit wakker worden. Dat is geen heuristiek — dat is een bewijs.
Hetzelfde ontwerp geeft u de beperking cadeau: als het kanaal of de mutex bereikbaar is via een globale variabele, of via een lokale variabele van een goroutine die nog steeds draait, kan de GC het nog steeds zien, dus de runtime kan niets concluderen. Merk op dat zelfs het speelgoedvoorbeeld hierboven twee runtime.GC()-aanroepen nodig heeft om te rapporteren. Het zal niet alles vangen.
Het zal echter wel de klassieke "vergeten de context te annuleren, worker goroutine leeft voor eeuwig" vangen — wat de meeste lekken het grootste deel van de tijd zijn.
Als u net/http/pprof importeert, is het ook beschikbaar op /debug/pprof/goroutineleak. Koppel dat aan uw staging-omgeving, controleer het wekelijks en wees stilletjes geschokt.
Dit werk werd bijgedragen door Vlad Saioc bij Uber, die een drankje naar keuze verdient.
2.3. Tracebacks vertellen u nu welk verzoek is gestorven
Voor modules die Go 1.27 of later declareren, bevatten traceback-kopregels nu runtime/pprof goroutine-labels.
1func handle(ctx context.Context) {
2 var wg sync.WaitGroup
3 wg.Go(func() {
4 buf := make([]byte, 4<<10)
5 os.Stdout.Write(buf[:runtime.Stack(buf, true)])
6 })
7 wg.Wait()
8}
9
10func main() {
11 labels := pprof.Labels("request", "GET /orders/42", "tenant", "acme")
12 pprof.Do(context.Background(), labels, handle)
13}
1goroutine 3 [running] {request: "GET /orders/42", tenant: acme}:
2main.handle.func1()
3 /tmp/go127/tblabel/main.go:15 +0x40
4sync.(*WaitGroup).Go.func1()
5 ...
6goroutine 1 [sync.WaitGroup.Wait] {request: "GET /orders/42", tenant: acme}:
7...
Het belangrijke detail: labels worden overgeërfd door child goroutines. Goroutine 3 heeft nooit een label ingesteld. Het kreeg er een van zijn parent.
Dus de volgende keer dat productie deadlocked en iemand de SIGQUIT naar het proces stuurt, krijgt u in plaats van 4.000 goroutines die er allemaal identiek uitzien, 4.000 goroutines getagd met welk verzoek en welke tenant ze toebehoren. Drie regels pprof.Do bij uw verzoek-entrypoint kopen u dat.
Omdat labels dingen kunnen bevatten die u liever niet naar stderr dumpt, schakelt GODEBUG=tracebacklabels=0 het uit, en die opt-out is expliciet bedoeld om voor altijd te blijven bestaan.
2.4. asynctimerchan is voorgoed verdwenen
Go 1.23 maakte timer-kanalen ongebufferd (synchroon) en bood asynctimerchan=1 als een weg terug naar het oude gedrag. In 1.27 is die instelling permanent verwijderd. time package-kanalen zijn synchroon, punt uit, geen onderhandeling mogelijk.
Het interessante deel is het beleid dat er naast werd geïntroduceerd. Een verwijderde GODEBUG die in uw go.mod achterblijft, breekt de build niet automatisch — het breekt alleen als het is ingesteld op de oude waarde:
1# go.mod bevat: godebug asynctimerchan=1
2go: error loading go.mod:
3go.mod:5: removed GODEBUG "asynctimerchan" set to old value "1" (https://go.dev/doc/godebug#go-127)
4
5# go.mod bevat: godebug asynctimerchan=0
6(buildt prima)
Wat betekent dat de enige mensen die op hun kop krijgen, de mensen zijn die daadwerkelijk vertrouwden op het verwijderde gedrag. Iedereen die het had ingesteld op de uiteindelijke standaard en het vergat, hoeft er niet meer over na te denken. Dat is een doordacht stukje API-archeologie.
3. Standaardbibliotheek
3.1. encoding/json/v2: Ze hebben de engine tijdens de vlucht vervangen
Dit is de grote. Ruim twee jaar voorsteldiscussie is eindelijk geland.
Er zijn nu drie packages, en het begrijpen van de splitsing is het halve werk:
| Package | Taak |
|---|---|
encoding/json | De v1 API die u kent. Gedrag 100% ongewijzigd. Nu geïmplementeerd bovenop v2 |
encoding/json/v2 | Semantische verwerking. Go-waarden ↔ JSON |
encoding/json/jsontext | Syntactische verwerking. JSON als een token-stream |
De kop is dat encoding/json is herbouwd op een compleet andere implementatie en zich identiek gedraagt. Zo werkt dat:
1// Go 1.27's encoding/json.Unmarshal, verkort
2func Unmarshal(data []byte, v any) error {
3 return jsonv2.Unmarshal(data, v, DefaultOptionsV1())
4}
Elke legacy v1-eigenaardigheid is gecodeerd als een optie, gebundeld in DefaultOptionsV1(), en de v1 API past altijd de bundel toe. Wat betekent dat dit zich hetzelfde gedraagt in 1.26 en 1.27:
1var m map[string]int
2json.Unmarshal([]byte(`{"a":1,"a":2}`), &m) // <nil>, map[a:2]
Ja, v1 accepteert nog steeds stilletjes dubbele keys en neemt de laatste. Dat deed het altijd al. Dat doet het nog steeds. Compatibiliteit betekent ook compatibiliteit met de slechte delen.
v2 heeft ondertussen een mening:
1var m map[string]int
2jsonv2.Unmarshal([]byte(`{"a":1,"a":2}`), &m)
3// jsontext: duplicate object member name "a"
4
5var s string
6jsonv2.Unmarshal([]byte("\"\xff\""), &s)
7// jsontext: invalid UTF-8 after offset 1
Dubbele keys en ongeldige UTF-8 worden geweigerd. Beide zijn oprecht gevaarlijk, niet alleen slordig — wanneer twee parsers het oneens zijn over welke dubbele key wint, krijgt u beveiligingsfouten. CouchDB's CVE-2017-12635 was precies dit: een JSON-body met twee roles-keys, waarbij de validator de ene las en de opslaglaag de andere. Weigeren is de juiste beslissing.
Opties zijn variadic:
1type Config struct {
2 Name string `json:"name"`
3 Tags []string `json:"tags,omitzero"`
4 Timeout int `json:"timeout,omitzero"`
5}
6
7out, _ := jsonv2.Marshal(Config{Name: "api"})
8// {"name":"api"}
9
10// gesorteerde map-keys + inspringing
11out, _ = jsonv2.Marshal(map[string]int{"b": 2, "a": 1},
12 jsonv2.Deterministic(true), jsontext.WithIndent(" "))
13// {
14// "a": 1,
15// "b": 2
16// }
17
18// weiger onbekende velden — voorheen vereiste dit een Decoder en DisallowUnknownFields()
19var c Config
20err := jsonv2.Unmarshal([]byte(`{"name":"api","nope":1}`), &c,
21 jsonv2.RejectUnknownMembers(true))
22// json: cannot unmarshal JSON string into Go main.Config:
23// unknown object member name "nope"
Deterministic, MatchCaseInsensitiveNames, StringifyNumbers, FormatNilSliceAsNull, OmitZeroStructFields — de meeste dingen die u voorheen oploste met een externe bibliotheek of een handgeschreven MarshalJSON zijn nu flags.
En het migratieverhaal is het beste deel. Latere opties winnen, dus u kunt de strengheid van v2 één gedrag per keer adopteren:
1// behoud v1-semantiek, maar weiger dubbele keys zoals v2 doet
2jsonv2.Unmarshal(data, &v,
3 json.DefaultOptionsV1(),
4 jsontext.AllowDuplicateNames(false))
5// duplicate object member name
U hoeft geen codebase van 200.000 regels naar v2 te porten om te stoppen met het accepteren van dubbele keys. U zet één optie om. Voor alles wat groot is, is dit het realistische pad.
Prestaties: marshaling is ongeveer gelijk, unmarshaling is aanzienlijk sneller. Als het misgaat, herstelt GOEXPERIMENT=nojsonv2 de oude implementatie — en die opt-out staat uiteindelijk ook op de nominatie om te verdwijnen, dus dien een issue in in plaats van eraan te wennen.
jsontext is de low-level laag: Encoder/Decoder die door JSON loopt als Tokens en Values met een state machine die u eerlijk houdt. Gebruik dit als u een streaming transformer of een JSON-filter schrijft en helemaal geen Go-waarden wilt materialiseren.
3.2. Een standaard uuid package
Eindelijk. RFC 9562, in de standaardbibliotheek, geen go get vereist.
1import "uuid"
2
3func main() {
4 fmt.Println(uuid.NewV4())
5 // b97aa695-da08-472c-af81-ff088129019f
6
7 // v7: de bovenste 48 bits zijn een timestamp, dus deze sorteren altijd in aanmaakvolgorde
8 a, b := uuid.NewV7(), uuid.NewV7()
9 fmt.Println(a.Compare(b) < 0) // true
10
11 u, err := uuid.Parse("urn:uuid:0198a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b")
12 fmt.Println(u, err)
13 // 0198a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b <nil>
14
15 fmt.Println(uuid.Nil(), uuid.Max())
16 // 00000000-0000-0000-0000-000000000000 ffffffff-ffff-ffff-ffff-ffffffffffff
17}
De hele API is New, NewV4, NewV7, Nil, Max, Parse, MustParse, en één type: type UUID [16]byte. Dat is het. U kunt de volledige documentatie van het package lezen terwijl uw koffie afkoelt.
Details die de moeite waard zijn:
UUIDis[16]byte, dus==werkt en het is direct bruikbaar als map-key. Zelfde ontwerp alsgoogle/uuid.NilenMaxzijn functies, geen variabelen. Omdat eenvar Nil UUIDop package-niveau een geladen pistool is dat op uw voet gericht staat, en iemand, ergens, zou er uiteindelijk aan toewijzen.- Willekeurige bits komen van een cryptografisch veilige generator.
- Het implementeert
encoding.TextMarshaler/TextUnmarshaler/TextAppender, dus het past direct in JSON-structs. NewV7is degene die u wilt voor primaire keys in databases. Tijd-geordend betekent dat uw B-tree index stopt met fragmenteren, waar v4 UUID's berucht slecht in zijn.
U kunt nu een afhankelijkheid verwijderen. Als u v1/v3/v5 of chiquere parseeropties nodig heeft, hebben externe bibliotheken nog steeds bestaansrecht.
3.3. crypto/mldsa: Post-quantum signatures, en ze zijn enorm
Go 1.24 gaf ons crypto/mlkem voor post-quantum key exchange. Go 1.27 brengt de andere helft: ML-DSA signatures, gestandaardiseerd als FIPS 204.
1sk, _ := mldsa.GenerateKey(mldsa.MLDSA65())
2pk := sk.PublicKey()
3
4msg := []byte("release the gophers")
5opts := &mldsa.Options{Context: "gosuda.org/blog"}
6
7sig, _ := sk.Sign(rand.Reader, msg, opts)
8
9fmt.Println("private key seed:", len(sk.Bytes()), "bytes") // 32
10fmt.Println("public key:", len(pk.Bytes()), "bytes") // 1952
11fmt.Println("signature:", len(sig), "bytes") // 3309
12
13fmt.Println(mldsa.Verify(pk, msg, sig, opts))
14// <nil>
15fmt.Println(mldsa.Verify(pk, msg, sig, &mldsa.Options{Context: "other"}))
16// mldsa: invalid signature
Kijk naar die getallen. Een enkele signature is 3.309 bytes. Ed25519 is 64. Dat is een toename van 50x, en de public key is er bijna 2KB bovenop. Stop er een paar in een certificaatketen en uw TLS-handshake begint zijn eigen MTU-strategie nodig te hebben.
Dit zijn de werkelijke kosten van quantum-resistentie vandaag de dag, en daarom gaat niemand alles morgen al omzetten. Maar het zit nu in de standaardbibliotheek, en dat is waar u het wilt hebben voordat u het nodig heeft.
Options.Context is domeinscheiding: teken met dezelfde sleutel voor verschillende doeleinden, gebruik voor elk een andere context, en een signature uit de ene context zal niet verifiëren in de andere. Het voorbeeld hierboven laat precies dat zien — dezelfde sleutel, hetzelfde bericht, andere context, geweigerd.
PrivateKey implementeert crypto.Signer, dus het past in bestaande interfaces. crypto/x509 verwerkt ML-DSA sleutels en signatures, en crypto/tls ondersteunt de MLDSA44/MLDSA65/MLDSA87 signature schemes in TLS 1.3.
Er is ook SignDeterministic, die de willekeur overslaat — handig voor tests en reproduceerbare builds.
3.4. simd: Vectorinstructies zonder assembly
Go 1.26 introduceerde de architectuur-specifieke simd/archsimd als experiment. Go 1.27 voegt simd toe — draagbaar en vectorbreedte-agnostisch. Schakel in met GOEXPERIMENT=simd.
1// dst = a*x + y
2func axpy(dst, x, y []float32, a float32) {
3 va := simd.BroadcastFloat32s(a)
4 w := va.Len()
5
6 i := 0
7 for ; i+w <= len(x); i += w {
8 vx := simd.LoadFloat32s(x[i:])
9 vy := simd.LoadFloat32s(y[i:])
10 vx.MulAdd(va, vy).Store(dst[i:])
11 }
12 // handel de staart af met een gedeeltelijke load/store — geen scalaire opruimlus
13 if i < len(x) {
14 vx, _ := simd.LoadFloat32sPart(x[i:])
15 vy, _ := simd.LoadFloat32sPart(y[i:])
16 vx.MulAdd(va, vy).StorePart(dst[i:])
17 }
18}
1$ GOEXPERIMENT=simd go1.27rc3 run ./simddemo
2vector bits: 128 emulated: false
3[4 7 10 13 16 19 22 25 28 31]
De ontwerpkeuze die ertoe doet: de vectorbreedte is nooit hardcoded. U vraagt va.Len() tijdens runtime. VectorBitSize() vertelt u de werkelijke breedte, Emulated() vertelt u of u echte hardware heeft of een beleefde software-imitatie. Mijn Mac rapporteerde 128-bit NEON; een AVX-512 machine rapporteert meer; een machine met niets rapporteert emulatie en de code draait nog steeds.
LoadFloat32sPart/StorePart verdienen speciale vermelding. Het meest irritante deel van handgeschreven SIMD is altijd de rafelige staart aan het einde van de array, en dit handelt het af zonder een aparte scalaire lus.
Nog steeds experimenteel, API nog steeds onstabiel, zet het niet in productie. Maar het feit dat dit Go-code is en geen assembly of cgo is een oprecht grote zaak.
3.5. hash/maphash.Hasher
Een nieuwe interface die het contract beschrijft tussen een value-type en op hash gebaseerde containers:
1type Hasher[T any] interface {
2 Hash(*Hash, T)
3 Equal(x, y T) bool
4}
Waarom? Go's ingebouwde map accepteert alleen comparable keys. U kunt niet keyen op een slice. U kunt niet definiëren "gelijk ongeacht hoofdlettergebruik". Hasher lost beide tegelijk op:
1type CaseInsensitive struct{}
2
3func (CaseInsensitive) Hash(h *maphash.Hash, s string) {
4 h.WriteString(strings.ToLower(s))
5}
6func (CaseInsensitive) Equal(x, y string) bool {
7 return strings.ToLower(x) == strings.ToLower(y)
8}
9
10var seed = maphash.MakeSeed()
11
12func hashOf[T any](hr maphash.Hasher[T], v T) uint64 {
13 var h maphash.Hash
14 h.SetSeed(seed) // zelfde seed, anders betekent dit niets
15 hr.Hash(&h, v)
16 return h.Sum64()
17}
18
19fmt.Println(hashOf(CaseInsensitive{}, "Go") == hashOf(CaseInsensitive{}, "GO"))
20// true
Voor gewone == semantiek is er ComparableHasher[T]:
1hashOf(maphash.ComparableHasher[int]{}, 42)
Het addertje onder het gras: Hasher is een interface, geen datastructuur. Er is nog geen standaard hashtabel of Bloom-filter die het consumeert. Dit is de basis voor een toekomstig container-package. Vandaag zou u het gebruiken bij het bouwen van uw eigen structuur, of een bestaande implementatie consumeren zoals go/types.Hasher (waarmee u types.Type als map-key kunt gebruiken, met respect voor Identical).
Seed-beheer is aan u. Als hashOf hierboven bij elke aanroep een verse seed maakte, zouden identieke waarden verschillend hashen en zou het voorbeeld false printen — wat precies de bug was die ik in mijn eerste poging schreef. Eén seed per container. (De seed wordt gerandomiseerd om hash-flooding DoS-aanvallen te verslaan, wat de reden is dat het niet zomaar een constante is.)
3.6. httptest.NewTestServer + synctest.Sleep: De onverwachte hit
Als u slechts één ding uit deze release adopteert, laat het dan deze zijn.
testing/synctest studeerde af in Go 1.25 en gaf ons een nepklok voor het testen van gelijktijdige code. Behalve dat op het moment dat uw test een echt netwerk aanraakte, de illusie instortte. Go 1.27's httptest.NewTestServer gebruikt een in-memory nep-netwerk, dus de bubbel blijft intact. En synctest.Sleep (= time.Sleep + synctest.Wait) maakt het compleet.
Hier is een retry-with-exponential-backoff test:
1func TestRetryWithBackoff(t *testing.T) {
2 synctest.Test(t, func(t *testing.T) {
3 var hits int
4 srv := httptest.NewTestServer(t, http.HandlerFunc(
5 func(w http.ResponseWriter, r *http.Request) {
6 hits++
7 if hits < 3 {
8 w.WriteHeader(http.StatusServiceUnavailable)
9 return
10 }
11 io.WriteString(w, "ok")
12 }))
13
14 client := srv.Client()
15 start := time.Now()
16 var body string
17 for attempt := range 5 {
18 resp, err := client.Get(srv.URL)
19 if err != nil {
20 t.Fatal(err)
21 }
22 b, _ := io.ReadAll(resp.Body)
23 resp.Body.Close()
24 if resp.StatusCode == http.StatusOK {
25 body = string(b)
26 break
27 }
28 synctest.Sleep(time.Duration(1<<attempt) * time.Second)
29 }
30
31 if body != "ok" {
32 t.Fatalf("got %q", body)
33 }
34 // backoff zou precies 1s + 2s = 3s moeten zijn
35 if elapsed := time.Since(start); elapsed != 3*time.Second {
36 t.Fatalf("elapsed = %v, want 3s", elapsed)
37 }
38 t.Logf("hits=%d elapsed=%v", hits, time.Since(start))
39 })
40}
1=== RUN TestRetryWithBackoff
2 x_test.go:48: hits=3 elapsed=3s
3--- PASS: TestRetryWithBackoff (0.00s)
Lees dat twee keer. Het simuleerde drie seconden backoff tegen een echte HTTP-server, en eindigde in 0,00 seconden. En de assertie is elapsed == 3*time.Second — precies drie seconden, niet "minstens drie seconden, met wat jitter van de scheduler". Nepklokken jitteren niet.
synctest.Sleep bestaat om een specifieke reden: als uw test slaapt voor dezelfde duur als de code onder test, is het maar de vraag wie er als eerste wakker wordt. synctest.Sleep slaapt en dan wacht totdat elke andere goroutine in de bubbel duurzaam geblokkeerd is, dus u observeert het systeem nadat het tot rust is gekomen.
Timeouts, retries, circuit breakers, rate limiters — elke tijdsafhankelijke HTTP-clienttest die u bezit kan snel en deterministisch worden. Als uw testsuite momenteel bij elkaar wordt gehouden met time.Sleep(100 * time.Millisecond) en hoop, is dit uw uitgang.
3.7. net/http-wijzigingen die daadwerkelijk productie beïnvloeden
Stil, maar ze zullen verschijnen in uw statistieken.
HTTP/1 response bodies lopen automatisch leeg bij Close. Ongelezen inhoud wordt nu leeggehaald (tot een conservatieve limiet) wanneer u de body sluit, zodat de verbinding opnieuw kan worden gebruikt. Wat betekent dat u eindelijk deze bezwering kunt verwijderen die iedereen sinds 2016 van hetzelfde Stack Overflow-antwoord heeft gekopieerd:
1// niet langer nodig
2defer func() {
3 io.Copy(io.Discard, resp.Body)
4 resp.Body.Close()
5}()
Voor de meeste programma's is dit een no-op of een kleine winst. Als het dingen slechter maakt, zit u waarschijnlijk in de categorie die de release notes beleefd beschrijven: Transport.MaxIdleConns ingesteld op 0, of een nieuwe Client per request, waardoor de idle connection limit volledig wordt omzeild. Transport.DisableKeepAlives = true zal het maskeren, maar het werkelijke advies van de release notes is dat "een diepere blik waarschijnlijk nuttig zou zijn", wat Go-team-taal is voor u heeft grotere problemen.
HTTP/2 client priority (RFC 9218). De server respecteert nu client-prioriteitssignalen. Als u de voorkeur gaf aan de oude round-robin scheduling, Server.DisableClientPriority = true.
Server.MaxHeaderValueCount. Begrenst hoeveel header-waarden de server accepteert, met DefaultMaxHeaderValueCount als standaard. Eén deur extra gesloten voor "stuur tienduizend headers en kijk wat er gebeurt".
ALPN op door gebruiker geleverde conns. Als uw net.Conn ConnectionState() tls.ConnectionState implementeert, zullen Transport en Server TLS ALPN-onderhandeling daarop uitvoeren — dus aangepaste dialers die door een proxy gaan, kunnen nog steeds HTTP/2 onderhandelen.
3.8. De kleinigheidjes die vreugde opwekken
strings.CutLast / bytes.CutLast. Cut splitst bij de eerste separator. Er was geen "laatste"-variant, dus iedereen schreef zelf LastIndex plus slicing, en ongeveer 30% van ons zat er de eerste keer eentje naast.
1name, ext, ok := strings.CutLast("archive.tar.gz", ".")
2// archive.tar gz true
Geweldig voor bestandsextensies en voor het parsen van host:port — waar u de laatste dubbele punt moet vinden, omdat IPv6-adressen er vol mee staan.
math/big.Int.Divide. Deling met een expliciete afrondingsmodus, ter vervanging van het "is het Quo of Div wat ik wil?" kop-of-muntspel:
1x, y := big.NewInt(-7), big.NewInt(2)
2
3new(big.Int).Divide(x, y, new(big.Int), big.Trunc) // q=-3 r=-1
4new(big.Int).Divide(x, y, new(big.Int), big.Floor) // q=-4 r=1
5new(big.Int).Divide(x, y, new(big.Int), big.Round) // q=-4 r=1
6new(big.Int).Divide(x, y, new(big.Int), big.Ceil) // q=-3 r=-1
Als u werkt in een domein waar de afrondingsregel is vastgelegd in een reglement, is dit voor u.
url.URL.Clone en url.Values.Clone. Diepe kopieën. De oude *u oppervlakkige kopie deelde de Userinfo-pointer, wat precies het soort bug opleverde waar je een dag over doet om te vinden en vijf seconden om te repareren.
1orig, _ := url.Parse("https://gosuda.org/blog?tag=go&tag=1.27")
2clone := orig.Clone()
3clone.Host = "example.com"
4fmt.Println(orig.Host, clone.Host) // gosuda.org example.com
net.UnixConn read-methoden retourneren nu direct io.EOF in plaats van het te verpakken in net.OpError. err == io.EOF werkt nu, zoals het altijd had moeten doen.
database/sql.ConvertAssign en driver.RowsColumnScanner. Functies voor driver-auteurs. De eerste stelt de type-conversies bloot die Rows.Scan uitvoert; de tweede laat drivers direct scannen in gebruikersbestemmingen, waarbij een tussenliggende allocatie wordt overgeslagen.
unicode 15 → 17. Twee versies in één sprong. String-classificatie en normalisatiegedrag kunnen subtiel verschuiven. Als u tests heeft die ervan afhangen, zult u erachter komen.
compress/flate is sneller geworden — en de uitvoerbytes kunnen verschillen van Go 1.26. Dit werkt door naar archive/zip, compress/gzip, compress/zlib, en image/png. Als u golden-file tests heeft die gecomprimeerde uitvoer hashen, gaan die kapot, en dat is geen regressie. Controleer dit voordat u upgradet, niet tijdens de incidentevaluatie.
4. Toolchain
4.1. go fix heeft meer modernizers gekregen
go fix verandert stilletjes in een code-moderniseringstool. Go 1.27 voegt atomictypes, embedlit, slicesbackward, en unsafefuncs toe. Gebruik -diff voor een preview:
1package fixdemo
2
3import (
4 "sync"
5 "sync/atomic"
6)
7
8type Base struct{ ID int }
9type User struct {
10 Base
11 Name string
12}
13
14func New() User {
15 return User{Base: Base{ID: 1}, Name: "gopher"}
16}
17
18var counter int64
19
20func Incr() { atomic.AddInt64(&counter, 1) }
21
22func Reverse(s []string) {
23 for i := len(s) - 1; i >= 0; i-- {
24 _ = s[i]
25 }
26}
27
28func Spawn(wg *sync.WaitGroup) {
29 wg.Add(1)
30 go func() {
31 defer wg.Done()
32 }()
33}
1$ go1.27rc3 fix -diff ./fixdemo
1 import (
2+ "slices"
3 "sync"
4 "sync/atomic"
5 )
6
7 func New() User {
8- return User{Base: Base{ID: 1}, Name: "gopher"}
9+ return User{ID: 1, Name: "gopher"}
10 }
11
12-var counter int64
13+var counter atomic.Int64
14
15-func Incr() { atomic.AddInt64(&counter, 1) }
16+func Incr() { counter.Add(1) }
17
18 func Reverse(s []string) {
19- for i := len(s) - 1; i >= 0; i-- {
20- _ = s[i]
21+ for _, v := range slices.Backward(s) {
22+ _ = v
23 }
24 }
25
26 func Spawn(wg *sync.WaitGroup) {
27- wg.Add(1)
28- go func() {
29- defer wg.Done()
30- }()
31+ wg.Go(func() {
32+ })
33 }
Vier modernizers geactiveerd op één klein bestand:
embedlit— herschrijft embedded literals met de nieuwe syntaxis uit §1.2atomictypes—atomic.AddInt64(&x, 1)wordtatomic.Int64.Add(1), wat niet-atomische toegang onmogelijk maakt en stilletjes 32-bit uitlijningsbugs repareert waarvan u niet wist dat u ze hadslicesbackward— achterwaartse lussen wordenslices.Backwardwaitgroupgo— hetAdd/go/Doneritueel wordtwg.Go(hernoemd van 1.26'swaitgroupom ambiguïteit te voorkomen)
go tool fix help somt alle 26 op; go tool fix help <name> legt er een uit. Ook werd fmtappendf verwijderd "vanwege stilistische zorgen", wat een prachtig diplomatieke manier is om te beschrijven wat er in die issue-thread gebeurde.
go fix ./... draaien op een oude codebase is een oprecht bevredigende middag. Lees natuurlijk eerst de diff.
4.2. go test draait stdversion standaard
Dit is de wijziging die het meest waarschijnlijk uw dag onderbreekt.
go test draait nu standaard de stdversion vet-check, die standaardbibliotheek-symbolen markeert die nieuwer zijn dan wat de go-richtlijn van uw go.mod toestaat:
1// go.mod zegt: go 1.24
2package sv
3
4import "strings"
5
6func Ext(name string) string {
7 _, ext, _ := strings.CutLast(name, ".") // CutLast is een 1.27 symbool
8 return ext
9}
1$ go1.27rc3 test ./...
2# sv
3./x.go:6:23: strings.CutLast requires go1.27 or later (module is go1.24)
4FAIL sv [build failed]
Dit doodt de klassieke foutmodus waarbij alles werkt op uw machine (nieuwste toolchain) en explodeert in CI of in de oudere omgeving van een gebruiker. Als u bibliotheken publiceert met een conservatieve go-richtlijn, is dit een cadeau.
4.3. go test -json heeft OutputType gekregen
"Action":"output" regels bevatten nu een optioneel "OutputType" veld: "error", "error-continue", of "frame".
1'frame' '=== RUN TestFail\n'
2None ' x_test.go:6: hello\n'
3'error' ' x_test.go:7: boom\n'
4'frame' '--- FAIL: TestFail (0.00s)\n'
5'frame' 'FAIL\n'
t.Log-uitvoer (geen veld), t.Error-uitvoer (error), en door de framework gegenereerde regels (frame) zijn nu onderscheidbaar. Iedereen die een regex heeft geschreven om uit te vogelen welke regels testuitvoer "echt" zijn, kan die nu verwijderen.
4.4. go doc verbeteringen
package@version syntaxis. Lees de documentatie voor een specifieke versie zonder deze toe te voegen aan uw module:
1$ go1.27rc3 doc golang.org/x/sync/errgroup@v0.10.0
2package errgroup // import "golang.org/x/sync/errgroup"
3...
Handig om te controleren wat er is veranderd voor een upgrade.
-ex en het afdrukken van voorbeeldbroncode:
1$ go1.27rc3 doc -ex strings | grep Example
2 func ExampleClone()
3 func ExampleCompare()
4 func ExampleContains()
5 func ExampleCut()
6 func ExampleCutPrefix()
7 ...
8
9$ go1.27rc3 doc strings.ExampleCut
10package main
11
12import (
13 "fmt"
14 "strings"
15)
16
17func main() {
18 show := func(s, sep string) {
19 before, after, found := strings.Cut(s, sep)
20 fmt.Printf("Cut(%q, %q) = %q, %q, %v\n", s, sep, before, after, found)
21 }
22 show("Gopher", "Go")
23 ...
24}
25
26Output:
27Cut("Gopher", "Go") = "", "pher", true
Voorbeeldbroncode en verwachte uitvoer, in de terminal, zonder een browser-tab te openen die volgende week donderdag nog steeds open staat.
4.5. go mod tidy ruimt uw require-blokken op
Voor modules op go 1.27 of later voegt go mod tidy verspreide require-blokken samen tot maximaal twee (direct en indirect), waarbij bijgevoegde opmerkingen behouden blijven.
1// Voorheen
2module tidydemo
3
4go 1.27
5
6require golang.org/x/sync v0.10.0
7
8// networking
9require golang.org/x/net v0.33.0
10
11require (
12 golang.org/x/sys v0.28.0 // indirect
13)
14
15require golang.org/x/text v0.21.0 // indirect
1// Nadien: go mod tidy
2module tidydemo
3
4go 1.27
5
6require (
7 // networking
8 golang.org/x/net v0.33.0
9 golang.org/x/sync v0.10.0
10)
Merk op dat de // networking opmerking overleefde. Dit ruimt grotendeels op na Git merge conflict resolutie, waar verdwaalde require-blokken worden geboren. Als uw team meer dan drie mensen heeft die afhankelijkheden toevoegen, zullen uw go.mod conflicten merkbaar rustiger worden.
4.6. Diversen
bzrondersteuning verwijderd. Als dit u raakt, zou ik oprecht het verhaal willen horen.go tool trace -httpbindt aan localhost wanneer alleen een poort wordt gegeven.-http=:6060luistert niet langer op elke interface. Gebruik-http=0.0.0.0:6060als u dat bedoelde. Dit komt nu overeen metgo tool pprof, en voorkomt de incidentele accidentele publieke profiler.- Response files (
@file) worden ondersteund doorcompile,link,asm,cgo,cover, enpack, in een GCC-compatibel formaat. Voor build-systemen die de command-line lengtelimieten overschrijden — hallo, Bazel.
5. Compiler, Linker, Ports
Compiler. Relatieve bestandsnamen in //line richtlijnen resolven nu tegen de directory van het bestand dat ze bevat, passend bij go/scanner. Relevant als u codegeneratoren schrijft.
Functieliteral (closure) symboolnamen zijn nu ook eenvoudiger — dezelfde naam ongeacht inlining, en meerdere instanties van dezelfde literal kunnen code delen in de binary. Geen functionele wijziging, behalve: code die functie-identiteit vergelijkt via reflect.Value.Pointer zal vaker "gelijk" zien dan voorheen. Die vergelijking was nooit geldig, maar als u die heeft, is dit het moment waarop die harder tegen u begint te liegen.
Linker. Nieuwe -macos en -macsdk opties stellen de OS- en SDK-versies in in het macOS LC_BUILD_VERSION load-commando.
Ports.
- Darwin vereist nu macOS 13 Ventura of later, zoals aangekondigd in 1.26. Controleer uw CI-runners.
- PowerPC (
GOOS=linux GOARCH=ppc64) is overgeschakeld naar de ELFv2 ABI. Vereist Linux-kernel 3.13+ (RHEL7 backported naar 3.10). Cgo, PIE, en externe linking worden nu ondersteund. Als u cgo gebruikt maar een statische pure-Go binary nodig heeft, stelCGO_ENABLED=0in.
6. Upgrade checklist
Go 1.27 neemt compatibiliteit serieus, maar controleer dit voordat u de versie verhoogt:
- Golden-file tests op gecomprimeerde uitvoer.
compress/flateheeft encoders veranderd; gzip/zip/png bytes kunnen verschillen. - Tests die matchen op JSON-foutmelding-strings. Gedrag is identiek; de fouttekst niet.
- Verwijderde GODEBUGs in
go.mod.asynctimerchan,gotypesalias,tlsrsakex,tls3des,tls10server,tlsunsafeekm,x509keypairleaf— deze laten de build alleen falen als ze op hun oude waarden staan. -
stdversionschendingen.go testvangt deze nu. Draai het vroeg als u een bibliotheek onderhoudt met een conservatievego-richtlijn. - macOS 12 of oudere CI-runners. Ondersteuning is verdwenen.
- Tests die asserten op functieliteral-symboolnamen, en
reflect.Value.Pointerfunctie-vergelijkingen. -
Transport.MaxIdleConns = 0of eenClientper request. Gecombineerd met automatisch leeglopende response bodies kan dit trager worden.
Afsluitende gedachten
Go 1.27 is de release waar een hoop uitgesteld onderhoud tegelijkertijd moest worden ingehaald.
Generieke methoden waren het ontbrekende stuk van het 1.18 generics-werk. Struct literal field selectors sloten een elf jaar oud issue. encoding/json/v2 was twee jaar voorsteldiscussie. En uuid vulde een gat dat in feite elk Go-project al een decennium met dezelfde externe afhankelijkheid aan het patchen was.
Als u een prioriteitsvolgorde wilt om hier daadwerkelijk waarde uit te halen:
httptest.NewTestServer+synctest.Sleep— tijdsafhankelijke HTTP-tests worden snel en deterministisch. Beste inzet-tot-rendement ratio in de hele release.- De goroutine leak profile — stel één endpoint bloot in staging en bereid u voor om nederig te worden.
- Traceback goroutine labels — drie regels
pprof.Dobij uw verzoek-entrypoint veranderen uw volgende 3-uur-'s-nachts stackdump in iets leesbaars. go fix ./...— laat de tool het moderniseren doen.encoding/json/v2— geen haast. Adopteer het één optie per keer.
Pak go1.27rc3 en draai uw testsuite er nu tegenaan. Alles op die checklist is aanzienlijk prettiger om te ontdekken op een dinsdagmiddag dan tijdens een incident.
Referenties