Go 1.27, eli miten opin lakkaamaan huolehtimasta ja rakastamaan geneerisiä metodeja


Go 1.27 julkaistaan elokuussa 2026, ja tällä kertaa itse kieli muuttui. Kyse ei ole muutoksesta, jossa "lisäsimme slices-pakettiin uuden funktion", vaan todellisesta muutoksesta. Geneeriset metodit ovat mukana. Yksitoista vuotta vanha issue suljettiin. encoding/json korvattiin hiljaa uudella moottorilla, kun lentokone oli ilmassa.
Jokainen alla oleva esimerkki ajettiin versiolla go1.27rc3 (darwin/arm64). Jokainen tulostuslohko on todellinen tulostus, kopioitu sellaisenaan, virheviestit mukaan lukien. Jos jokin näyttää oudolta, syy on se, että kääntäjä todella sanoi niin.
1go install golang.org/dl/go1.27rc3@latest
2go1.27rc3 download
1. Kieli muuttui (kyllä, todella)
1.1. Geneeriset metodit
Go 1.18:sta lähtien meillä on ollut geneerisyys, ja Go 1.18:sta lähtien meillä on ollut myös seuraava keskustelu. Se etenee näin:
"Kirjoitetaan vain
Map-metodi omalle slice-tyypilleni..."
method must have no type parameters"...no hyvä on. Tehdään sitten paketin tason funktio."
Metodit saattoivat käyttää vain vastaanottajan (receiver) määrittelemiä tyyppiparametreja. Oma metodi ei voinut esitellä uusia. Niinpä jokainen geneerinen muunnos karkotettiin paketin tasolle, jossa se istui seitsemäntoista muun vapaasti leijuvan apufunktion vieressä, joiden nimet olivat jotakin muunnelmaa nimestä MapSlice.
Go 1.27 korjaa tämän:
1type List[E any] []E
2
3// F on metodin itsensä määrittelemä tyyppiparametri
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}
Vastaanottajan ei tarvitse edes olla geneerinen. Tavallinen struct voi saada geneerisen metodin:
1type Bag struct{ items []any }
2
3func (b *Bag) Add[T any](v T) { b.items = append(b.items, v) }
Ennen kuin refaktoroit koko koodikantasi tänä iltapäivänä, on huomattava, että rajoituksia on kaksi, ja ne ovat tärkeämpiä kuin miltä näyttävät:
- Rajapintametodit eivät voi määritellä tyyppiparametreja.
- Geneeriset metodit eivät voi toteuttaa rajapintametodeja.
Tuo jälkimmäinen on se, joka kostautuu:
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
Geneeriset metodit ja dynaaminen lähetys eivät siis sovi yhteen. Kyse ei ole Go-tiimin ahneudesta: geneerisellä metodilla on äärettömän monta instanssia, kun taas rajapintametodien taulukon on oltava äärellinen ja käännösaikana tunnettu. Äärettömää asiaa ei voi laittaa äärelliseen taulukkoon. Universumi sanoi ei.
Käytännön johtopäätös: geneeriset metodit sopivat konkreettisille tyypeille, joilla on hyödyllisyyttä korostavia API:ja. Kaikki, mitä täytyy piilottaa rajapinnan taakse, tarvitsee yhä vanhan lähestymistavan.
Standardikirjasto hyödynsi tätä jo. math/rand/v2:n Rand sai geneerisen metodin:
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
Aiemmin ainoastaan paketin tason rand.N oli geneerinen, mikä tarkoitti globaalin lähteen käyttöä. Nyt oma siemennetty *Rand saa saman mukavuuden. Pieni asia. Mukava asia.
1.2. Struct-literaalien kenttävalitsimet eli issue #9859 viimein pääsee loppuun
Upotus (embedding) antaa sinulle u.ID. Upotus ei anna sinulle User{ID: 1}. Sen sijaan se antaa User{Base: Base{ID: 1}}, mikä on Go-kielen tapa kysyä, tarkoititko todella sitä.
Issue #9859 avattiin vuonna 2015. Se on nyt suljettu. Jossain päin maailmaa gopher, joka on sittemmin vaihtanut uraa kahdesti, saa GitHub-ilmoituksen.
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: käytä nostettua kenttää suoraan
12line := Line{name: "diagonal", q: Point3D{y: -4, z: 12.3}}
name ei ole Line-tyypin kenttä. Se on upotetun Object-tyypin kenttä. Aiemmin olisit kirjoittanut Line{Object: Object{name: "diagonal"}, ...}.
Nyt tulee pienempi kirjoitus, jonka tarkistin todella yrittämällä kääntää kaiken.
(a) Avain on yhä yksinkertainen tunniste. Mielivaltaista valitsinpolkua ei voi kirjoittaa.
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
Ominaisuus ei siis tarkoita sitä, että avaimet toimivat nyt kuten kenttäkäyttö. Se tarkoittaa nimenomaan sitä, että implisiittisesti nostettujen kenttien nimet ovat sallittuja. Se on hieman kapeampi kuin otsikko antaa ymmärtää.
(b) Et voi määritellä samanaikaisesti upotettua kenttää ja sen kautta nostettua kenttää.
1obj := Object{"edge", "black"}
2_ = Line{Object: obj, name: "diagonal"}
3// cannot specify promoted field name and enclosing embedded field Object
Järkevää. Kerroit kääntäjälle kaksi ristiriitaista asiaa samasta muistialueesta, ja se kieltäytyi arvaamasta.
(c) Osoittimella upottaminen ei ole toivottua.
1type PtrEmbed struct {
2 *Object
3 z int
4}
5
6_ = PtrEmbed{name: "x"}
7// invalid implicit pointer indirection to reach name
Osoittimen seuraamiseen tarvitaan osoitin, ja literaalia rakennettaessa sitä ei vielä ole. Tämäkin on oikeudenmukaista.
Hyvä uutinen: go fix kirjoittaa vanhat literaalisi uudelleen puolestasi. Siitä lisää myöhemmin.
1.3. Funktiotyyppien päättely on vähemmän mielivaltaista
Geneeristen funktioiden tyypinpäättely toimi aiemmin joissakin paikoissa mutta ei toisissa, eikä kummankaan tapauksen taustalla ollut havaittavaa periaatetta. Sijoitus muuttujaan? Toimii. Täsmälleen saman funktion asettaminen struct-kenttään? Kääntäjävirhe, kirjoita double[int] kuin vuonna 2022.
Go 1.27 tekee päättelystä toimivan jokaisessa kontekstissa, jossa kohdetyyppi on yksiselitteisesti tiedossa:
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: tarvittiin double[int]
8 a := A{double} // 1.26: tarvittiin double[int]
9
10 c := make(chan func(int) int, 1)
11 c <- double // 1.26: tarvittiin double[int]
12
13 var fn func(float64) float64 = double // tämä toimi aina
14
15 fmt.Println(s.f(21), a[0](5), (<-c)(7), fn(1.5))
16 // 42 10 14 3
17}
Yksi double instantioidaan tyypiksi func(int) int kolmessa paikassa ja tyypiksi func(float64) float64 neljännessä, kaikki pelkän kontekstin perusteella. Jos kirjoitat asetusrakenteita tai käsittelijätaulukoita, jotka ovat täynnä funktioarvoja, joukko [T]-kohinaa on pian poissa koodikannastasi.
2. Ajoaika: ilmaista suorituskykyä ja vuotojen tunnistin
2.1. Kokoon erikoistunut allokointi
Kääntäjä tuottaa nyt pienille olioille kutsuja kokoon erikoistuneisiin allokointirutiineihin. Julkaisutiedotteiden mukaan allokaatiot, jotka ovat alle 80 tavua, ovat jopa 30 % nopeampia ja allokointiintensiivisissä ohjelmissa kokonaisuutena noin 1 % nopeampia.
En usko julkaisutiedotteisiin ilman mittaamista, joten:
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# oletus (kokoon erikoistunut malloc päällä)
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
8
Mikrobenchmarkissa, joka ei tee muuta kuin allokoi Apple Silicon -piirillä, ero on 30 ja 35 % nopeampi. Toisin sanoen tämä on paras mahdollinen tapaus, saavutettu laboratorio-olosuhteissa benchmarkilla, joka on suunniteltu näyttämään luku hyvältä. Todellisessa ohjelmassa roskienkeruu ja varsinainen työ peittävät suurimman osan hyödystä. Noin 1 %:n väite on rehellinen.
Hinta näkyy binäärin koossa. Hello World:
12413202 bytes (oletus)
22361826 bytes (nosizespecializedmalloc)
Noin 50 kt kiinteä lisäys, riippumatta ohjelmastasi. Voit kytkeä tämän pois päältä asetuksella GOEXPERIMENT=nosizespecializedmalloc, mutta tämä pakotie on suunniteltu poistettavaksi Go 1.28:ssa, joten käsittele sitä vikailmoituksen kiertotienä, älä elämäntapana.
2.2. Goroutine-vuotoprofiili on nyt todellinen
Kokeellinen Go 1.26:ssa, yleisesti saatavilla versiossa 1.27. goroutineleakprofile-GOEXPERIMENT on poistettu; se vain toimii.
1func leak() {
2 ch := make(chan int) // kukaan ei koskaan lähetä tänne. ei kukaan.
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
Kolme goroutinea on pysyvästi jumissa, ja tiedoston nimi ja rivinumero osoittavat täsmälleen kohdan, jossa tein sen.
Tämän takana oleva temppu on todella nokkela: se käyttää uudelleen roskienkerääjän tavoitettavuusanalyysiä. Jos goroutine G on jumissa primitiivin P takana, ja P ei ole tavoitettavissa millekään ajettavalle goroutinelle (tai millekään, minkä nuo goroutinet voisivat herättää), kukaan ei voi koskaan enää koskea P:hen, joten G ei koskaan herää. Tämä ei ole heuristiikka, vaan todistus.
Sama suunnittelu antaa rajoituksen ilmaiseksi: jos kanava tai mutex on tavoitettavissa globaalin muuttujan kautta, tai jonkin yhä käynnissä olevan goroutinen paikallisen muuttujan kautta, roskienkerääjä näkee sen edelleen, joten ajoaika ei voi päätellä mitään. Huomaa, että jopa yllä oleva leikkiesimerkki tarvitsee kaksi kutsua runtime.GC():lle, jotta se raportoi. Se ei tavoita kaikkea.
Se kuitenkin tavoittaa klassisen "unohdin peruuttaa kontekstin, työntekijägoroutine elää ikuisesti" -tapauksen, joka on useimmiten suurin osa vuotoja.
Jos tuot paketin net/http/pprof, se löytyy myös osoitteesta /debug/pprof/goroutineleak. Kytke se staging-ympäristöön, tarkista viikoittain ja ole hiljaa kauhuissasi.
Tämän työn on tehnyt Vlad Saioc Uberiltä, ja hän ansaitsee juoman omasta valinnastaan.
2.3. Jäljitykset kertovat nyt, mikä pyyntö kaatui
Moduuleille, jotka ilmoittavat Go 1.27 tai uudemman version, jäljityksen otsikkorivit sisältävät nyt runtime/pprof-goroutine-tunnisteet.
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...
Tärkeä yksityiskohta: tunnisteet periytyvät lapsigoroutineille. Goroutine 3 ei koskaan asettanut tunnistetta. Se sai sellaisen vanhemmaltaan.
Seuraavan kerran, kun tuotanto jumittuu ja joku lähettää SIGQUIT-signaalin prosessille, sen sijaan että saisit 4 000 keskenään identtistä goroutinea, saat 4 000 goroutinea, joihin on merkitty, mihin pyyntöön ja mille vuokralaiselle ne kuuluvat. Kolme riviä pprof.Do-kutsua pyyntösi sisäänkäyntipisteessä tuo tämän.
Koska tunnisteet voivat sisältää asioita, joita et halua tulostaa stderr-virtaan, GODEBUG=tracebacklabels=0 kytkee ominaisuuden pois, ja tämä ulosjäämismahdollisuus on tarkoitettu säilyväksi ikuisesti.
2.4. asynctimerchan on poistunut lopullisesti
Go 1.23 teki ajastinkanavista puskuroimattomia (synkronisia) ja tarjosi asynctimerchan=1-asetuksen paluuksi vanhaan käyttäytymiseen. Versiossa 1.27 tämä asetus on poistettu pysyvästi. time-paketin kanavat ovat synkronisia, eikä niistä neuvotella.
Mielenkiintoinen osa on rinnakkain esitelty käytäntö. Poistettu GODEBUG, joka on jäänyt go.mod-tiedostoon, ei automaattisesti riko käännöstä. Se rikkoo sen vain, jos se on asetettu vanhaan arvoon:
1# go.mod sisältää: 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 sisältää: godebug asynctimerchan=0
6(builds fine)
Tämä tarkoittaa, että ainoat, jotka saavat huomautuksen, ovat ne, jotka todella luottivat poistettuun käyttäytymiseen. Kaikki, jotka asettivat asetuksen lopulliseen oletusarvoonsa ja unohtivat sen, saavat jatkaa olemista ajattelematta sitä. Se on harkittu esimerkki API-arkeologiasta.
3. Standardikirjasto
3.1. encoding/json/v2: moottori vaihdettiin lennon aikana
Tämä on suuri muutos. Yli kahden vuoden keskustelu ehdotuksesta saatiin vihdoin päätökseen.
Pakkauksia on nyt kolme, ja jaon ymmärtäminen on suurin osa taistelua:
| Pakkaus | Tehtävä |
|---|---|
encoding/json | Tuntemasi v1-rajapinta. Käyttäytyminen on 100 % ennallaan. Toteutettu nyt v2:n päällä |
encoding/json/v2 | Semanttinen käsittely. Go-arvot ↔ JSON |
encoding/json/jsontext | Syntaktinen käsittely. JSON tokenivirtana |
Pääotsikko on, että encoding/json rakennettiin uudelleen täysin erilaisen toteutuksen päälle, ja se käyttäytyy täsmälleen samoin. Näin:
1// Go 1.27:n encoding/json.Unmarshal, lyhennettynä
2func Unmarshal(data []byte, v any) error {
3 return jsonv2.Unmarshal(data, v, DefaultOptionsV1())
4}
Jokainen vanha v1-erikoisuus on koodattu asetukseksi, paketoitu DefaultOptionsV1()-funktioon, ja v1-rajapinta soveltaa aina tätä pakettia. Tämä tarkoittaa, että seuraava toimii samoin versioissa 1.26 ja 1.27:
1var m map[string]int
2json.Unmarshal([]byte(`{"a":1,"a":2}`), &m) // <nil>, map[a:2]
Kyllä, v1 hyväksyy yhä hiljaa kaksoiskappaleavaimet ja ottaa viimeisen. Se on aina tehnyt niin. Se tekee yhä. Yhteensopivuus tarkoittaa yhteensopivuutta myös huonojen osien kanssa.
v2 sen sijaan on mielipiteitä:
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
Kaksoiskappaleavaimet ja virheellinen UTF-8 hylätään. Molemmat ovat todella vaarallisia, eivät vain epäsiistejä: kun kaksi jäsennintä ovat eri mieltä siitä, kumpi kaksoiskappaleavaimista voittaa, seurauksena on tietoturvavirheitä. CouchDB:n CVE-2017-12635 oli juuri tällainen: JSON-runko, jossa oli kaksi roles-avainta, ja validoija luki toisen, tallennuskerros toisen. Hylkääminen on oikea ratkaisu.
Asetukset ovat muuttuvamäärisiä:
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// lajitellut map-avaimet + sisennys
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// hylkää tuntemattomat kentät — aiemmin tarvittiin Decoder ja 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: suurin osa asioista, jotka aiemmin ratkaistiin kolmannen osapuolen kirjastolla tai käsin kirjoitetulla MarshalJSON-metodilla, ovat nyt lippuja.
Ja siirtymätarina on parasta. Myöhemmät asetukset voittavat, joten voit ottaa v2:n tiukkuuden käyttöön yksi käyttäytyminen kerrallaan:
1// säilytä v1-semantiikka, mutta hylkää kaksoiskappaleavaimet kuten v2
2jsonv2.Unmarshal(data, &v,
3 json.DefaultOptionsV1(),
4 jsontext.AllowDuplicateNames(false))
5// duplicate object member name
Sinun ei tarvitse siirtää 200 000 rivin koodikantaa v2:een lopettaaksesi kaksoiskappaleavainten hyväksymisen. Käännät yhden asetuksen. Suurille kokonaisuuksille tämä on realistinen tie.
Suorituskyky: marshalointi on suunnilleen samalla tasolla, unmarshalointi on merkittävästi nopeampaa. Jos jokin menee pieleen, GOEXPERIMENT=nojsonv2 palauttaa vanhan toteutuksen. Myös tämä ulosjäämismahdollisuus on lopulta poistettavien listalla, joten tee vikailmoitus sen sijaan, että asettuisit siihen pysyvästi.
jsontext on matalan tason kerros: Encoder ja Decoder, jotka kulkevat JSON-dataa Token- ja Value-arvoina tilakoneen pitäessä sinut rehellisenä. Tartu siihen, kun kirjoitat suoratoistomuunninta tai JSON-suodatinta, eikä sinun tarvitse muuttaa Go-arvoja lainkaan.
3.2. Standardi uuid-paketti
Viimein. RFC 9562 standardikirjastossa, ilman go get-komentoa.
1import "uuid"
2
3func main() {
4 fmt.Println(uuid.NewV4())
5 // b97aa695-da08-472c-af81-ff088129019f
6
7 // v7: ylimmät 48 bittiä ovat aikaleima, joten nämä lajittuvat aina luontijärjestyksessä
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}
Koko API koostuu funktioista New, NewV4, NewV7, Nil, Max, Parse, MustParse ja yhdestä tyypistä: type UUID [16]byte. Siinä kaikki. Voit lukea koko paketin dokumentaation, kun kahvisi jäähtyy.
Yksityiskohdat, jotka kannattaa tietää:
UUIDon[16]byte, joten==toimii ja sitä voi käyttää suoraan map-avaimena. Sama suunnittelu kuingoogle/uuid:ssä.NiljaMaxovat funktioita, eivät muuttujia. Koska paketin tasonvar Nil UUIDon ladattu ase, joka on suunnattu jalkaasi, ja joku jossain lopulta sijoittaisi siihen.- Satunnaisbitit tulevat kryptografisesti turvallisesta generaattorista.
- Se toteuttaa
encoding.TextMarshaler-,TextUnmarshaler- jaTextAppender-rajapinnat, joten se sopii suoraan JSON-struct-tyyppeihin. NewV7on se, jonka haluat tietokannan pääavaimiin. Aikajärjestys tarkoittaa, että B-puu-indeksisi lakkaa pirstoutumasta, mihin v4-UUID:t ovat tunnetusti huonoja.
Voit nyt poistaa riippuvuuden. Jos tarvitset v1/v3/v5 tai monimutkaisempia jäsennysasetuksia, kolmannen osapuolen kirjastoilla on yhä tehtävä.
3.3. crypto/mldsa: kvanttiturvalliset allekirjoitukset, ja ne ovat valtavia
Go 1.24 toi crypto/mlkem-paketin kvanttiturvalliseen avainvaihtoon. Go 1.27 tuo toisen puoliskon: ML-DSA-allekirjoitukset, standardoituna nimellä 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
Katso noita lukuja. Yksi allekirjoitus on 3 309 tavua. Ed25519:n allekirjoitus on 64. Tämä on 50-kertainen kasvu, ja julkinen avain on sen lisäksi lähes 2 kt. Jos pakkaat muutaman näistä sertifikaattiketjuun, TLS-kättelysi tarvitsee oman MTU-strategiansa.
Tämä on kvanttikestävyyden todellinen hinta tänään, ja siksi kukaan ei vaihda kaikkea huomenna. Mutta se on nyt standardikirjastossa, mikä on juuri se paikka, jossa sen tulee olla, ennen kuin tarvitset sitä.
Options.Context on toimialueen erottelu: allekirjoita sama avain eri tarkoituksiin, käytä jokaiselle eri kontekstia, ja yhden kontekstin allekirjoitus ei vahvistu toisessa. Yllä oleva esimerkki näyttää tämän: sama avain, sama viesti, eri konteksti, hylätty.
PrivateKey toteuttaa crypto.Signer-rajapinnan, joten se sopii olemassa oleviin rajapintoihin. crypto/x509 käsittelee ML-DSA-avaimia ja allekirjoituksia, ja crypto/tls tukee allekirjoitusskeemoja MLDSA44, MLDSA65 ja MLDSA87 TLS 1.3:ssa.
Lisäksi on SignDeterministic, joka ohittaa satunnaisuuden. Se on kätevä testeissä ja toistettavissa käännöksissä.
3.4. simd: vektorikäskyt ilman assemblyä
Go 1.26 esitteli arkkitehtuurikohtaisen simd/archsimd-paketin kokeiluna. Go 1.27 lisää simd-paketin, joka on siirrettävä ja vektorileveydestä riippumaton. Ota käyttöön asetuksella 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 // käsittele häntä osittaisella lataus-/tallennusoperaatiolla — ei skalaarista siivousSilmukkaa
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]
Suunnitteluvalinta, joka on tärkeä: vektorileveyttä ei koskaan kovakoodata. Kysyt va.Len():ltä ajoaikana. VectorBitSize() kertoo todellisen leveyden, Emulated() kertoo, saitko oikean laitteiston vai kohteliaan ohjelmistollisen jäljittelyn. Mac-koneeni raportoi 128-bittisen NEONin; AVX-512 -koneet raportoivat enemmän; kone, jossa ei ole mitään, raportoi emulaation, ja koodi toimii silti.
LoadFloat32sPart/StorePart ansaitsevat erityismaininnan. Käsin kirjoitetun SIMD:n ärsyttävin osa on aina taulukon lopun epätasainen häntä, ja tämä käsittelee sen ilman erillistä skalaarista silmukkaa.
Yhä kokeellinen, API on yhä epävakaa, älä vie tuotantoon. Mutta se, että tämä on Go-koodia eikä assemblya tai cgo:ta, on todella suuri asia.
3.5. hash/maphash.Hasher
Uusi rajapinta, joka kuvaa arvotyypin ja hajautuspohjaisten säiliöiden välisen sopimuksen:
1type Hasher[T any] interface {
2 Hash(*Hash, T)
3 Equal(x, y T) bool
4}
Miksi? Go-kielen sisäänrakennettu map hyväksyy vain comparable-avaimet. Et voi käyttää slicea avaimena. Et voi määritellä "yhtäsuuruutta kirjainkoon huomioimatta". Hasher ratkaisee molemmat kerralla:
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) // sama siemen, tai tästä ei ole mitään hyötyä
15 hr.Hash(&h, v)
16 return h.Sum64()
17}
18
19fmt.Println(hashOf(CaseInsensitive{}, "Go") == hashOf(CaseInsensitive{}, "GO"))
20// true
Tavallista ==-semantiikkaa varten on ComparableHasher[T]:
1hashOf(maphash.ComparableHasher[int]{}, 42)
Saalis on tämä: Hasher on rajapinta, ei tietorakenne. Ei ole vielä standardia hajautustaulua tai Bloom-suodatinta, joka kuluttaisi sitä. Tämä on perusta tulevalle säiliöpaketille. Tänään käytät sitä, kun rakennat oman rakenteesi, tai käytät olemassa olevaa toteutusta, kuten go/types.Hasher:ia, jonka avulla voit käyttää types.Type-arvoa map-avaimena ja joka kunnioittaa Identical-vertailua.
Siemenen hallinta on sinun vastuullasi. Jos yllä oleva hashOf loisi uuden siemenen joka kutsulla, identtiset arvot hajautuisivat eri tavoin ja esimerkki tulostaisi false. Tämä on juuri se virhe, jonka kirjoitin ensimmäisellä yrityksellä. Yksi siemen per säiliö. (Siemen on satunnaistettu, jotta hash-flooding-DoS-hyökkäykset eivät onnistu, minkä vuoksi se ei ole vain vakio.)
3.6. httptest.NewTestServer + synctest.Sleep: uniheräte
Jos otat tästä julkaisusta vain yhden asian, ota tämä.
testing/synctest valmistui Go 1.25:ssä ja antoi meille keinotekoisen kellon samanaikaisen koodin testaukseen. Mutta heti kun testisi koski oikeaan verkkoon, illuusio romahti. Go 1.27:n httptest.NewTestServer käyttää muistissa olevaa keinotekoista verkkoa, joten kupla pysyy ehjänä. Ja synctest.Sleep (= time.Sleep + synctest.Wait) täydentää kokonaisuuden.
Tässä on eksponentiaalisella viiveellä toimivan uudelleenyrityksen testi:
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 // viiveen tulisi olla täsmälleen 1s + 2s = 3s
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)
Lue tämä kahdesti. Se simuloi kolme sekuntia viivettä todellista HTTP-palvelinta vastaan, ja valmistui ajassa 0,00 sekuntia. Ja väite on elapsed == 3*time.Second, täsmälleen kolme sekuntia, ei "vähintään kolme sekuntia, plus miinus ajastimen vaihtelu". Keinotekoiset kellot eivät vaihtele.
synctest.Sleep on olemassa tiettyä syytä varten: jos testisi nukkuu saman keston kuin testattava koodi, on arvaus, kumpi herää ensin. synctest.Sleep nukkuu ja odottaa sitten, kunnes kaikki muut kuplan goroutinet ovat pysyvästi jumissa, joten havainnoit järjestelmää sen jälkeen, kun se on asettunut.
Aikakatkaisut, uudelleenyritykset, katkaisijat ja nopeusrajoittimet: jokainen aikariippuvainen HTTP-asiakastesti, jonka omistat, voi muuttua nopeaksi ja deterministiseksi. Jos testisarjasi pysyy tällä hetkellä koossa time.Sleep(100 * time.Millisecond)-kutsulla ja toivolla, tämä on pääsysi.
3.7. net/http-muutokset, jotka todella vaikuttavat tuotantoon
Hiljaisia, mutta ne näkyvät mittareissasi.
HTTP/1-vastausrungot tyhjenevät automaattisesti Close-kutsussa. Lukematon sisältö tyhjennetään nyt (konservatiiviseen rajaan asti), kun suljet rungon, jotta yhteys voidaan käyttää uudelleen. Tämä tarkoittaa, että voit vihdoin poistaa tämän loitsun, jonka kaikki ovat kopioineet samasta Stack Overflow -vastauksesta vuodesta 2016 lähtien:
1// ei enää tarpeen
2defer func() {
3 io.Copy(io.Discard, resp.Body)
4 resp.Body.Close()
5}()
Useimmille ohjelmille tämä on ei-toiminto tai pieni voitto. Jos se tekee asioista huonompia, olet todennäköisesti julkaisutiedotteiden kohteliaasti kuvaamassa tilanteessa: Transport.MaxIdleConns on asetettu arvoon 0 tai jokaista pyyntöä varten luodaan uusi Client, mikä ohittaa vapaiden yhteyksien rajoituksen kokonaan. Transport.DisableKeepAlives = true peittää ongelman, mutta julkaisutiedotteiden varsinainen neuvo on, että "syvempi tarkastelu olisi todennäköisesti hyödyllinen", mikä Go-tiimin kielellä tarkoittaa sinulla on isompia ongelmia.
HTTP/2-asiakkaan prioriteetti (RFC 9218). Palvelin noudattaa nyt asiakkaan prioriteettisignaaleja. Jos pidit vanhasta round-robin-aikataulutuksesta, aseta Server.DisableClientPriority = true.
Server.MaxHeaderValueCount. Rajoittaa, kuinka monta otsikkoarvoa palvelin hyväksyy, oletusarvona DefaultMaxHeaderValueCount. Yksi ovi lisää suljettu kohdalle "lähetä kymmenen tuhatta otsikkoa ja katso, mitä tapahtuu".
ALPN käyttäjän antamissa yhteyksissä. Jos net.Conn toteuttaa metodin ConnectionState() tls.ConnectionState, Transport ja Server suorittavat TLS ALPN -neuvottelun sille. Näin mukautetut dialerit, jotka kulkevat välityspalvelimen kautta, voivat yhä neuvotella HTTP/2:n.
3.8. Pienet asiat, jotka tuovat iloa
strings.CutLast / bytes.CutLast. Cut jakaa ensimmäisen erottimen kohdalta. "Viimeistä" varianttia ei ollut, joten kaikki kirjoittivat LastIndex:n ja viipaloinnin käsin, ja noin 30 % meistä teki off-by-one-virheen ensimmäisellä yrityksellä.
1name, ext, ok := strings.CutLast("archive.tar.gz", ".")
2// archive.tar gz true
Erinomainen tiedostopäätteille ja host:port-muotoisen tekstin jäsentämiselle, jossa on löydettävä viimeinen kaksoispiste, koska IPv6-osoitteet ovat täynnä niitä.
math/big.Int.Divide. Jakolasku eksplisiittisellä pyöristystilalla, joka korvaa kysymyksen "tarkoitanko Quo:a vai Div:iä" kolikonheitolla:
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
Jos työskentelet alalla, jolla pyöristyssääntö on kirjattu säädökseen, tämä on sinua varten.
url.URL.Clone ja url.Values.Clone. Syvät kopiot. Vanha *u-matala kopio jakoi Userinfo-osoittimen, mikä aiheutti juuri sellaisen virheen, jonka löytäminen vie päivän ja korjaaminen viisi sekuntia.
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:n lukumetodit palauttavat nyt suoraan io.EOF:n sen sijaan, että kääriisivät sen net.OpError:iin. err == io.EOF toimii nyt, kuten sen olisi aina pitänyt.
database/sql.ConvertAssign ja driver.RowsColumnScanner. Ajurin kirjoittajan ominaisuuksia. Ensimmäinen paljastaa tyyppimuunnokset, joita Rows.Scan tekee; toinen mahdollistaa ajureiden lukea suoraan käyttäjän kohteisiin ohittaen välisen allokoinnin.
unicode 15 → 17. Kaksi versiota yhdellä hypyllä. Merkkijonojen luokittelu ja normalisointi saattavat muuttua hienovaraisesti. Jos sinulla on testejä, jotka riippuvat siitä, huomaat sen.
compress/flate on nopeampi, ja sen tuottamat tavut saattavat erota Go 1.26:sta. Tämä vaikuttaa myös archive/zip-, compress/gzip-, compress/zlib- ja image/png-paketteihin. Jos sinulla on golden-file-testejä, jotka tiivistävät pakatun tulosteen tiivisteeksi, ne hajoavat, eikä kyse ole regressiosta. Tarkista tämä ennen päivitystä, älä vasta vian jälkeisessä katselmoinnissa.
4. Työkaluketju
4.1. go fix sai lisää modernisoijia
go fix on hiljalleen muuttumassa koodin modernisointityökaluksi. Go 1.27 lisää atomictypes-, embedlit-, slicesbackward- ja unsafefuncs-modernisoijat. Esikatsele tuloksia komennolla -diff:
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 }
Neljä modernisoijaa laukesi yhdessä pienessä tiedostossa:
embedlit: kirjoittaa upotetut literaalit uudelleen §1.2:n uudella syntaksillaatomictypes:atomic.AddInt64(&x, 1)muuttuu muotoonatomic.Int64.Add(1), mikä tekee ei-atomisen käytön mahdottomaksi ja korjaa hiljaa 32-bittisen tasauksen virheet, joista et tiennytslicesbackward: takaperoiset silmukat muuttuvat muotoonslices.Backwardwaitgroupgo:Add/go/Done-rituaali muuttuu muotoonwg.Go(nimetty uudelleen versiosta 1.26 nimeltäwaitgroup, jotta vältetään epäselvyys)
go tool fix help listaa kaikki 26; go tool fix help <nimi> selittää yhden. Lisäksi fmtappendf poistettiin "tyylillisistä syistä", mikä on kaunis diplomaattinen tapa kuvata se, mitä tuon issue-ketjun aikana tapahtui.
Komennon go fix ./... ajaminen vanhalla koodikannalla on todella tyydyttävä iltapäivä. Lue diff ensin, tietenkin.
4.2. go test ajaa stdversion-tarkistuksen oletuksena
Tämä on muutos, joka todennäköisimmin keskeyttää päiväsi.
go test ajaa nyt oletuksena stdversion-vet-tarkistuksen, joka merkitsee standardikirjaston symbolit, jotka ovat uudempia kuin go.mod-tiedoston go-direktiivi sallii:
1// go.mod sanoo: go 1.24
2package sv
3
4import "strings"
5
6func Ext(name string) string {
7 _, ext, _ := strings.CutLast(name, ".") // CutLast on 1.27-symboli
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]
Tämä tappaa klassisen virhetilanteen, jossa kaikki toimii koneellasi (uusin työkaluketju) ja räjähtää CI:ssä tai käyttäjän vanhemmassa ympäristössä. Jos julkaiset kirjastoja konservatiivisella go-direktiivillä, tämä on lahja.
4.3. go test -json sai OutputType-kentän
"Action":"output"-rivit kantavat nyt valinnaisen kentän "OutputType": "error", "error-continue" tai "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-tulostus (ei kenttää), t.Error-tulostus (error) ja kehyksen tuottamat rivit (frame) voidaan nyt erottaa toisistaan. Kuka tahansa, joka on kirjoittanut säännöllisen lausekkeen selvittääkseen, mitkä testitulosteen rivit ovat "oikeita", voi nyt poistaa sen.
4.4. go doc -parannukset
package@version-syntaksi. Lue tietyn version dokumentaatio lisäämättä sitä moduuliisi:
1$ go1.27rc3 doc golang.org/x/sync/errgroup@v0.10.0
2package errgroup // import "golang.org/x/sync/errgroup"
3...
Kätevä tarkistamaan, mitä muuttui ennen päivitystä.
-ex ja esimerkkilähdekoodin tulostus:
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
Esimerkin lähdekoodi ja odotettu tulos, terminaalissa, avaamatta selainvälilehteä, joka on yhä auki ensi torstaina.
4.5. go mod tidy siivoaa require-lohkosi
Moduuleille, joissa on go 1.27 tai uudempi, go mod tidy yhdistää hajallaan olevat require-lohkot enintään kahteen (suorat ja epäsuorat) säilyttäen liitetyt kommentit.
1// Ennen
2module tidydemo
3
4go 1.27
5
6require golang.org/x/sync v0.10.0
7
8// verkko
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// Jälkeen: go mod tidy
2module tidydemo
3
4go 1.27
5
6require (
7 // verkko
8 golang.org/x/net v0.33.0
9 golang.org/x/sync v0.10.0
10)
Huomaa, että // verkko-kommentti säilyi. Tämä siivoaa ennen kaikkea Git-yhdistämiskonfliktien ratkaisun jäljet, sillä siellä hajallaan olevat require-lohkot syntyvät. Jos tiimissäsi on yli kolme ihmistä lisäämässä riippuvuuksia, go.mod-konfliktit ovat pian huomattavasti hiljaisempia.
4.6. Sekalaista
bzr-tuki poistettu. Jos tämä koskee sinua, haluaisin todella kuulla tarinasi.go tool trace -httpsitoutuu localhostiin, kun annetaan pelkkä portti.-http=:6060ei enää kuuntele kaikilla rajapinnoilla. Käytä-http=0.0.0.0:6060, jos tarkoitit sitä. Tämä vastaa nytgo tool pprof-komentoa ja estää satunnaisen vahingossa julkisen profiloijan.- Vastaustiedostot (
@file) ovat tuettuja komennoissacompile,link,asm,cgo,coverjapackGCC-yhteensopivassa muodossa. Build-järjestelmille, jotka ylittävät komentoriviksi rajan, hei, Bazel.
5. Kääntäjä, linkkeri, portit
Kääntäjä. Suhteelliset tiedostonimet //line-direktiiveissä ratkaistaan nyt sisältävän tiedoston hakemistoa vasten, samoin kuin go/scanner. Tämä on merkityksellistä, jos kirjoitat koodigeneraattoreita.
Funktioliteraalien (sulkeumien) symbolien nimet ovat myös yksinkertaisempia: sama nimi riippumatta inlinoinnista, ja saman literaalin useat esiintymät voivat jakaa koodin binäärissä. Toiminnallista muutosta ei ole, paitsi: koodi, joka vertaa funktion identiteettiä reflect.Value.Pointer:lla, näkee "yhtäsuuret" useammin kuin ennen. Tuo vertailu ei ole koskaan ollut pätevä, mutta jos sinulla on sellaista, nyt se alkaa valehdella sinulle kovemmalla äänellä.
Linkkeri. Uudet -macos- ja -macsdk-asetukset asettavat käyttöjärjestelmän ja SDK:n versiot macOS:n LC_BUILD_VERSION-latauskomentoon.
Portit.
- Darwin vaatii nyt macOS 13 Ventura -version tai uudemman, kuten ilmoitettiin versiossa 1.26. Tarkista CI-ajurisi.
- PowerPC (
GOOS=linux GOARCH=ppc64) siirtyi ELFv2 ABI:hin. Vaatii Linux-ytimen 3.13+ (RHEL7 takaportattu versioon 3.10). Cgo, PIE ja ulkoinen linkitys ovat nyt tuettuja. Jos käytät cgo:ta mutta tarvitset staattisen puhtaan Go-binäärin, asetaCGO_ENABLED=0.
6. Päivityksen tarkistuslista
Go 1.27 ottaa yhteensopivuuden vakavasti, mutta tarkista nämä ennen version nostamista:
- Golden-file-testit pakatusta tulosteesta.
compress/flatemuutti enkooderejaan; gzip-, zip- ja png-tavut saattavat erota. - Testit, jotka vertaavat JSON-virheviestien merkkijonoihin. Käyttäytyminen on identtinen; virheteksti ei ole.
- Poistetut GODEBUG-asetukset
go.mod-tiedostossa.asynctimerchan,gotypesalias,tlsrsakex,tls3des,tls10server,tlsunsafeekm,x509keypairleaf: nämä rikkovat käännöksen vain, jos ne on asetettu vanhoihin arvoihinsa. -
stdversion-rikkomukset.go testhavaitsee nämä nyt. Aja se aikaisin, jos ylläpidät kirjastoa konservatiivisellago-direktiivillä. - macOS 12 tai vanhemmat CI-ajurit. Tuki on poistunut.
- Testit, jotka väittävät funktioliteraalien symbolinimistä, sekä
reflect.Value.Pointer-funktiovertailut. -
Transport.MaxIdleConns = 0taiClientjokaista pyyntöä kohden. Yhdessä automaattisen vastausrungon tyhjentämisen kanssa tämä saattaa hidastaa toimintaa.
Loppuajatuksia
Go 1.27 on julkaisu, jossa paljon lykättyä ylläpitoa erääntyi kerralla.
Geneeriset metodit olivat puuttuva pala Go 1.18:n geneerisyystyössä. Struct-literaalien kenttävalitsimet sulkivat yksitoista vuotta vanhan issuen. encoding/json/v2 oli kahden vuoden ehdotuskeskustelu. Ja uuid täytti aukon, jonka lähes jokainen Go-projekti oli vuosikymmenen ajan paikannut samalla kolmannen osapuolen riippuvuudella.
Jos haluat tärkeysjärjestyksen tämän hyödyntämiseen käytännössä:
httptest.NewTestServer+synctest.Sleep: aikariippuvaisista HTTP-testeistä tulee nopeita ja deterministisiä. Paras panostuksen ja hyödyn suhde koko julkaisussa.- Goroutine-vuotoprofiili: avaa yksi päätepiste staging-ympäristössä ja valmistaudu nöyrtymään.
- Jäljityksen goroutine-tunnisteet: kolme riviä
pprof.Do:a pyyntösi sisäänkäyntipisteessä muuttaa seuraavan kello 3 yöllä otettavan pinovedoksen luettavaksi. go fix ./...: anna työkalun hoitaa modernisointi.encoding/json/v2: ei kiirettä. Ota se käyttöön yksi asetus kerrallaan.
Hanki go1.27rc3 ja aja testisarjasi sitä vasten jo nyt. Kaikki tarkistuslistan kohdat on huomattavasti mukavampi löytää tiistai-iltapäivänä kuin vikatilanteen aikana.
Lähteet