GoSuda

Go 1.27 eli kuinka opin lakkaamaan murehtimasta ja rakastamaan geneerisiä metodeja

By iwanhae
views ...

Go 1.27 -yleiskatsaus

Go 1.27 julkaistaan elokuussa 2026, ja tällä kertaa itse kieli on muuttunut. Kyse ei ole vain "lisäsimme uuden funktion slices-pakettiin" -tyyppisestä muutoksesta, vaan todellisesta muutoksesta. Geneeriset metodit ovat nyt käytössä. Yksitoista vuotta vanha ongelma on ratkaistu. encoding/json korvattiin hiljaisesti uudella moottorilla lennosta.

Jokainen alla oleva esimerkki on ajettu go1.27rc3-versiolla (darwin/arm64). Jokainen tulostelohko sisältää aitoa, kopioitua tulostetta, mukaan lukien virheilmoitukset. Jos jokin näyttää oudolta, se johtuu siitä, että kääntäjä ilmoitti asian juuri niin.

1go install golang.org/dl/go1.27rc3@latest
2go1.27rc3 download

1. Kieli muuttui (kyllä, todellakin)

1.1. Geneeriset metodit

Go 1.18:sta lähtien meillä on ollut geneerisyys, ja siitä asti olemme käyneet tiettyä keskustelua. Se kuuluu näin:

"Sallikaa minun vain kirjoittaa Map-metodi slice-tyypilleni—"

method must have no type parameters

"...selvä. Käytetään siis pakettitason funktiota."

Metodit pystyivät käyttämään vain vastaanottajan (receiver) määrittelemiä tyyppiparametreja. Oma metodi ei voinut esitellä uusia. Niinpä jokainen geneerinen muunnos karkotettiin pakettitasolle, missä se istui seitsemäntoista muun vapaasti kelluvan apufunktion vieressä, joiden nimi oli jokin variaatio MapSlice-nimestä.

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 sisältää 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ään iltapäivällä, on huomioitava kaksi rajoitusta, jotka ovat merkittävämpiä kuin miltä ne näyttävät:

  1. Rajapintojen (interface) metodit eivät voi määritellä tyyppiparametreja.
  2. Geneeriset metodit eivät voi toteuttaa rajapintojen metodeja.

Jälkimmäinen rajoitus on se, joka aiheuttaa ongelmia:

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 (dynamic dispatch) eivät siis sovi yhteen. Go-tiimi ei ole tässä kitsas — kyse on siitä, että geneerisellä metodilla on ääretön määrä instansseja, ja rajapinnan metoditaulun on oltava äärellinen ja tiedossa käännösaikana. Ääretöntä asiaa ei voi sijoittaa äärelliseen tauluun. Universumi sanoi ei.

Käytännön tulkinta: geneeriset metodit on tarkoitettu konkreettisille tyypeille, joilla on apufunktioiden kaltaisia rajapintoja. Kaikki, minkä on piilouduttava rajapinnan taakse, vaatii edelleen vanhaa lähestymistapaa.

Standardikirjasto hyödyntää tätä jo. math/rand/v2-paketin Rand-tyyppi 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 vain pakettitason rand.N oli geneerinen, mikä tarkoitti globaalin lähteen käyttöä. Nyt omalla alustetulla *Rand-instanssillasi on sama mukavuus. Pieni asia. Mukava asia.

1.2. Struct-literaalien kenttävalitsimet, eli: ongelma #9859 on vihdoin ratkaistu

Upottaminen (embedding) antaa sinulle u.ID. Upottaminen ei kuitenkaan anna sinulle User{ID: 1}. Sen sijaan se antaa User{Base: Base{ID: 1}}, mikä on Gon tapa kysyä, tarkoititko varmasti sitä.

Ongelma #9859 raportoitiin vuonna 2015. Se on nyt suljettu. Jossain päin maailmaa gopher, joka on vaihtanut alaa 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ä ylennettyä 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"}, ...}.

Tässä on pientä pränttiä, jonka tarkistin yrittämällä kääntää kaiken:

(a) Avain on edelleen tavallinen tunniste. Et voi kirjoittaa mielivaltaista valitsinpolkua.

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 ole "avaimet toimivat nyt kuten kenttävalinta". Se on nimenomaan "implisiittisesti ylennetyt kenttien nimet ovat sallittuja". Hieman kapeampi kuin otsikko antaa ymmärtää.

(b) Et voi määrittää upotettua kenttää ja jotain siitä ylennettyä kenttää samanaikaisesti.

1obj := Object{"edge", "black"}
2_ = Line{Object: obj, name: "diagonal"}
3// cannot specify promoted field name and enclosing embedded field Object

Järkevää. Annoit sille kaksi ristiriitaista ohjetta samasta muistialueesta, ja se kieltäytyi arvaamasta.

(c) Osoittimien upottaminen ei ole mukana.

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, jota seurata, eikä literaalin luontihetkellä sellaista vielä ole. Myös tämä on kohtuullista.

Hyviä uutisia: go fix kirjoittaa vanhat literaalisi puolestasi. Lisää tästä myöhemmin.

1.3. Funktiotyyppien päättely muuttui vähemmän mielivaltaiseksi

Geneeristen funktioiden tyyppipäättely toimi aiemmin joissakin paikoissa mutta ei toisissa, ilman selkeää periaatetta. Muuttujaan sijoittaminen? Toimi. Saman funktion asettaminen struct-kenttään? Kääntäjävirhe, kirjoita double[int] kuin eletään vuotta 2022.

Go 1.27 tekee päättelystä toimivaa 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: vaati double[int]
 8	a := A{double}             // 1.26: vaati double[int]
 9
10	c := make(chan func(int) int, 1)
11	c <- double                // 1.26: vaati 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, joka instansioituu muodossa func(int) int kolmessa paikassa ja func(float64) float64 neljännessä, täysin kontekstin perusteella. Jos kirjoitat optio-structeja tai funktioarvoja täynnä olevia käsittelijätauluja, suuri määrä [T]-kohinaa on poistumassa koodikannastasi.

2. Runtime: Ilmaista suorituskykyä ja vuotojen tunnistin

2.1. Kokokohtainen allokointi

Kääntäjä tuottaa nyt kutsuja kokokohtaisiin allokointirutiineihin pienille objekteille. Julkaisutiedot lupaavat jopa 30 % vähennyksen alle 80 tavun allokoinneissa ja noin 1 % kokonaisparannuksen allokointipainotteisissa ohjelmissa.

En usko julkaisutietoihin ilman mittauksia, 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 (kokokohtainen malloc käytössä)
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

30–35 % nopeampi mikrobänchmarkissa, joka tekee vain allokointia Apple Siliconilla. Toisin sanoen: tämä on paras mahdollinen tapaus, joka saavutettiin laboratoriotiloissa bänchmarkilla, joka on suunniteltu näyttämään hyvältä. Oikeassa ohjelmassa roskienkeruu ja varsinainen työ peittävät suurimman osan tästä. ~1 % väite on rehellinen.

Hinta on binäärin koko. Hello World:

12413202 tavua  (oletus)
22361826 tavua  (nosizespecializedmalloc)

Noin 50 kt, kiinteästi, ohjelmastasi riippumatta. Voit poistaa sen käytöstä GOEXPERIMENT=nosizespecializedmalloc-asetuksella, mutta tämä pakoaukko on tarkoitus poistaa Go 1.28 -versiossa, joten käsittele sitä vikailmoituksen kiertotapana pikemminkin kuin pysyvänä ratkaisuna.

2.2. Goroutine-vuotoprofiili on nyt todellinen

Kokeellisena Go 1.26:ssa, yleisesti saatavilla 1.27:ssä. goroutineleakprofile GOEXPERIMENT on poissa; se vain toimii.

 1func leak() {
 2	ch := make(chan int) // kukaan ei tule koskaan lähettämään. 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, pysyvästi jumissa, tiedosto- ja rivinumerolla, joka osoittaa tarkalleen, missä teit sen.

Tämän takana oleva temppu on aidosti nerokas: se uudelleenkäyttää roskienkerääjän saavutettavuusanalyysia. Jos goroutine G on estetty primitiivillä P, ja P on saavuttamattomissa mistään ajettavasta goroutinesta (tai mistään, minkä nuo goroutinet voisivat herättää), mikään ei voi enää koskaan koskea P:hen, joten G ei koskaan herää. Se ei ole heuristiikka — se on todistus.

Sama suunnittelu antaa rajoituksen ilmaiseksi: jos kanava tai mutex on saavutettavissa globaalin muuttujan kautta tai jonkin edelleen käynnissä olevan goroutinen paikallisen muuttujan kautta, roskienkerääjä näkee sen yhä, joten runtime ei voi päätellä mitään. Huomaa, että jopa yllä oleva leikkiesimerkki vaatii kaksi runtime.GC()-kutsua raportointiin. Se ei havaitse kaikkea.

Se havaitsee kuitenkin klassisen "unohdin peruuttaa kontekstin, työntekijä-goroutine elää ikuisesti" -tilanteen, joka on yleisin vuotojen aiheuttaja.

Jos importoat net/http/pprof, se löytyy myös osoitteesta /debug/pprof/goroutineleak. Kytke se staging-ympäristöön, tarkista se viikoittain ja ole hiljaa kauhistunut.

Tämän työn toteutti Vlad Saioc Uberilta, joka ansaitsee valitsemansa juoman.

2.3. Jäljitykset kertovat nyt, mikä pyyntö kuoli

Moduuleille, jotka ilmoittavat Go 1.27:n tai uudemman, jäljityksen (traceback) 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 lapsi-goroutinille. Goroutine 3 ei koskaan asettanut tunnistetta. Se sai sen vanhemmaltaan.

Joten seuraavan kerran, kun tuotantoympäristö lukkiutuu ja joku lähettää prosessille SIGQUIT-signaalin, saat 4 000 identtiseltä näyttävän goroutinen sijaan 4 000 goroutinea, jotka on merkitty sillä, mihin pyyntöön ja tenanttiin ne kuuluvat. Kolme riviä pprof.Do-koodia pyyntösi sisääntulopisteessä ostaa sinulle tämän.

Koska tunnisteet voivat sisältää asioita, joita et halua dumpata stderr-virtaan, GODEBUG=tracebacklabels=0 kytkee sen pois päältä, ja tuo pakoaukko on tarkoitus säilyttää ikuisesti.

2.4. asynctimerchan on poistettu lopullisesti

Go 1.23 teki ajastinkanavista puskuroimattomia (synkronisia) ja tarjosi asynctimerchan=1-asetuksen paluutiensä vanhaan käyttäytymiseen. 1.27-versiossa tämä asetus on poistettu pysyvästi. time-paketin kanavat ovat synkronisia, piste, ei neuvotteluvaraa.

Mielenkiintoinen osa on sen rinnalla esitelty käytäntö. Poistettu GODEBUG, joka on jätetty go.mod-tiedostoosi, ei automaattisesti riko buildia — se rikkoutuu 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 ihmiset, joille huudetaan, ovat ne, jotka todella luottivat poistettuun käyttäytymiseen. Kaikki muut, jotka asettivat sen lopulliseen oletusarvoon ja unohtivat asian, voivat jatkaa sen murehtimatta. Se on harkittu pala API-arkeologiaa.

3. Standardikirjasto

3.1. encoding/json/v2: moottori vaihdettiin lennosta

Tämä on suuri muutos. Yli kaksi vuotta kestänyt ehdotuskeskustelu on vihdoin tullut maaliin.

Nyt on kolme pakettia, ja jaon ymmärtäminen on puoli voittoa:

PakettiTehtävä
encoding/jsonTuntemasi v1 API. Käyttäytyminen 100 % muuttumaton. Nyt toteutettu v2:n päällä
encoding/json/v2Semanttinen käsittely. Go-arvot ↔ JSON
encoding/json/jsontextSyntaktinen käsittely. JSON token-virtana

Pääuutinen on, että encoding/json rakennettiin uudelleen täysin eri toteutuksella ja se käyttäytyy identtisesti. Näin se toimii:

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 v1-version perintöominaisuus koodattiin optioksi, niputettiin DefaultOptionsV1()-funktioon, ja v1 API soveltaa aina tätä nippua. Tämä tarkoittaa, että tämä käyttäytyy samalla tavalla 1.26- ja 1.27-versioissa:

1var m map[string]int
2json.Unmarshal([]byte(`{"a":1,"a":2}`), &m)  // <nil>, map[a:2]

Kyllä, v1 hyväksyy edelleen hiljaisesti duplikaattiavaimet ja ottaa viimeisen. Se teki niin aina. Se tekee niin edelleen. Yhteensopivuus tarkoittaa yhteensopivuutta myös huonojen osien kanssa.

v2 puolestaan omaa 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

Duplikaattiavaimet ja virheellinen UTF-8 hylätään. Molemmat ovat aidosti vaarallisia, eivät vain siisteyteen liittyviä — kun kaksi jäsennintä on eri mieltä siitä, kumpi duplikaattiavain voittaa, syntyy tietoturva-aukkoja. CouchDB:n CVE-2017-12635 oli juuri tätä: JSON-runko, jossa oli kaksi roles-avainta, joista validoija luki toisen ja tallennuskerros toisen. Hylkääminen on oikea ratkaisu.

Optiot ovat variadisia:

 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// järjestetyt 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 vaati Decoder-olion ja DisallowUnknownFields()-kutsun
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 — useimmat asiat, jotka aiemmin ratkaisit kolmannen osapuolen kirjastolla tai käsin kirjoitetulla MarshalJSON-funktiolla, ovat nyt lippuja.

Ja migraatiotarina on paras osa. Viimeiset optiot voittavat, joten voit ottaa v2:n tiukkuuden käyttöön yksi käyttäytyminen kerrallaan:

1// säilytä v1-semantiikka, mutta hylkää duplikaattiavaimet kuten v2 tekee
2jsonv2.Unmarshal(data, &v,
3	json.DefaultOptionsV1(),
4	jsontext.AllowDuplicateNames(false))
5// duplicate object member name

Sinun ei tarvitse portata 200 000 rivin koodikantaa v2-versioon lopettaaksesi duplikaattiavainten hyväksymisen. Käännät vain yhden option. Suurille kokonaisuuksille tämä on realistinen polku.

Suorituskyky: marshaling on suunnilleen samalla tasolla, unmarshaling on merkittävästi nopeampaa. Jos jokin menee vikaan, GOEXPERIMENT=nojsonv2 palauttaa vanhan toteutuksen — ja tuokin pakoaukko on lopulta poistumassa, joten tee mieluummin vikailmoitus kuin asetu aloillesi.

jsontext on matalan tason kerros: Encoder/Decoder kävelevät JSON-dataa Token- ja Value-muodossa tilakoneen pitäessä sinut kurissa. Käytä sitä, kun kirjoitat striimaavaa muunninta tai JSON-suodatinta etkä halua materialisoida Go-arvoja lainkaan.

3.2. Standardi uuid-paketti

Vihdoinkin. RFC 9562, standardikirjastossa, ei vaadi 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ä järjestyvät 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 on New, NewV4, NewV7, Nil, Max, Parse, MustParse ja yksi tyyppi: type UUID [16]byte. Se siitä. Voit lukea koko paketin dokumentaation kahvin jäähtyessä.

Yksityiskohtia, jotka kannattaa tietää:

  • UUID on [16]byte, joten == toimii ja se on suoraan käytettävissä map-avaimena. Sama suunnittelu kuin google/uuid-paketissa.
  • Nil ja Max ovat funktioita, eivät muuttujia. Koska pakettitason var Nil UUID on ladattu ase osoitettuna jalkaasi, ja joku, jossain, määrittäisi siihen lopulta arvon.
  • Satunnaiset bitit tulevat kryptografisesti turvallisesta generaattorista.
  • Se toteuttaa encoding.TextMarshaler/TextUnmarshaler/TextAppender-rajapinnat, joten se toimii suoraan JSON-structeissa.
  • NewV7 on se, jonka haluat tietokannan perusavaimiksi. Aikaan perustuva järjestys tarkoittaa, että B-puu-indeksisi lakkaa pirstoutumasta, missä v4-UUID-tunnisteet ovat tunnetusti huonoja.

Voit nyt poistaa riippuvuuden. Jos tarvitset v1/v3/v5-versioita tai hienompia jäsennysvaihtoehtoja, kolmannen osapuolen kirjastoilla on yhä paikkansa.

3.3. crypto/mldsa: Kvanttitietokoneiden kestävät allekirjoitukset, ja ne ovat valtavia

Go 1.24 antoi meille crypto/mlkem-paketin kvanttitietokoneiden kestävään avainten vaihtoon. Go 1.27 tuo toisen puolikkaan: ML-DSA-allekirjoitukset, standardoituna FIPS 204 -muodossa.

 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 on 64. Se on 50-kertainen lisäys, ja julkinen avain on lähes 2 kt sen päälle. Ahtaa muutama näistä varmenteeseen, niin TLS-kättelysi alkaa vaatia omaa MTU-strategiaa.

Tämä on kvanttikestävyyden todellinen hinta tänään, ja siksi kukaan ei vaihda kaikkea huomenna. Mutta se on nyt standardikirjastossa, missä haluat sen olevan ennen kuin tarvitset sitä.

Options.Context on domain-erottelu: allekirjoita samalla avaimella eri tarkoituksiin, käytä eri kontekstia kullekin, niin yhden kontekstin allekirjoitus ei verifioidu toisessa. Yllä oleva esimerkki näyttää juuri sen — 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 MLDSA44/MLDSA65/MLDSA87-allekirjoitusmenetelmiä TLS 1.3 -versiossa.

Mukana on myös SignDeterministic, joka ohittaa satunnaisuuden — kätevä testeille ja toistettaville build-versioille.

3.4. simd: Vektorikäskyjä ilman assemblyä

Go 1.26 esitteli arkkitehtuurikohtaisen simd/archsimd-paketin kokeiluna. Go 1.27 lisää simd-paketin — kannettavan ja vektorin leveydestä riippumattoman. Ota käyttöön GOEXPERIMENT=simd-asetuksella.

 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 loppupää osittaisella latauksella/tallennuksella — ei skalaarisiivoussilmukkaa
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]

Suunnittelupäätös, jolla on merkitystä: vektorin leveyttä ei ole koskaan kovakoodattu. Kysyt va.Len()-arvoa ajonaikana. VectorBitSize() kertoo todellisen leveyden, Emulated() kertoo, saitko todellista laitteistoa vai kohteliaan ohjelmistototeutuksen. Macini raportoi 128-bittisen NEON-yksikön; AVX-512-kone raportoi enemmän; kone ilman SIMD-tukea raportoi emulaation ja koodi silti toimii.

LoadFloat32sPart/StorePart ansaitsevat erityismaininnan. Käsin kirjoitetun SIMD-koodin ärsyttävin osa on aina taulukon lopussa oleva pirstaleinen osuus, ja tämä hoitaa sen ilman erillistä skalaarisilmukkaa.

Yhä kokeellinen, API yhä epävakaa, älä laita tuotantoon. Mutta se fakta, että tämä on Go-koodia eikä assemblyä tai cgo-koodia, on aidosti suuri juttu.

3.5. hash/maphash.Hasher

Uusi rajapinta, joka kuvaa arvo-tyypin ja hash-pohjaisten säiliöiden välistä sopimusta:

1type Hasher[T any] interface {
2	Hash(*Hash, T)
3	Equal(x, y T) bool
4}

Miksi? Gon sisäänrakennettu map hyväksyy vain comparable-avaimia. Et voi käyttää slice-tyyppiä avaimena. Et voi määritellä "yhtäsuuruus ilman kirjainkokoa". 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 seed, tai muuten tällä ei ole merkitystä
15	hr.Hash(&h, v)
16	return h.Sum64()
17}
18
19fmt.Println(hashOf(CaseInsensitive{}, "Go") == hashOf(CaseInsensitive{}, "GO"))
20// true

Tavalliselle ==-semantiikalle on olemassa ComparableHasher[T]:

1hashOf(maphash.ComparableHasher[int]{}, 42)

Saalis: Hasher on rajapinta, ei tietorakenne. Ei ole vielä olemassa standardia hash-taulua tai Bloom-suodatinta, joka kuluttaisi sitä. Tämä on perustyötä tulevaa säiliöpakettia varten. Tänään käyttäisit sitä omaa rakennetta luodessasi tai kuluttaessasi olemassa olevaa toteutusta, kuten go/types.Hasher (joka antaa käyttää types.Type-tyyppiä map-avaimena, kunnioittaen Identical-ehtoa).

Seed-hallinta on sinun vastuullasi. Jos yllä oleva hashOf loisi uuden seed-arvon jokaisella kutsulla, identtiset arvot saisivat eri hash-arvot ja esimerkki tulostaisi false — mikä on juuri se bugi, jonka kirjoitin ensimmäisellä yrittämällä. Yksi seed per säiliö. (Seed on satunnaistettu hash-tulva-DoS-hyökkäysten estämiseksi, minkä vuoksi se ei ole vain vakio.)

3.6. httptest.NewTestServer + synctest.Sleep: Yllätysmenestys

Jos otat tästä julkaisusta käyttöön vain yhden asian, valitse tämä.

testing/synctest valmistui Go 1.25-versiossa ja antoi meille valekellon samanaikaisen koodin testaamiseen. Mutta heti kun testisi kosketti oikeaa verkkoa, illuusio romahti. Go 1.27:n httptest.NewTestServer käyttää muistissa olevaa valeverkkoa, joten kupla pysyy ehjänä. Ja synctest.Sleep (= time.Sleep + synctest.Wait) täydentää sen.

Tässä on testi uudelleenyritykselle eksponentiaalisella backoff-toiminnolla:

 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		// backoffin pitäisi olla tasan 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 tuo kahdesti. Se simuloi kolmen sekunnin backoff-toiminnon oikeaa HTTP-palvelinta vasten ja valmistui 0,00 sekunnissa. Ja väite on elapsed == 3*time.Secondtasan kolme sekuntia, ei "vähintään kolme sekuntia, riippuen schedulerin jitteristä". Valekellot eivät jitteröi.

synctest.Sleep on olemassa tietystä syystä: jos testisi nukkuu saman ajan kuin testattava koodi, kumpi herää ensin, on kenen tahansa arvattavissa. synctest.Sleep nukkuu ja sen jälkeen odottaa, kunnes jokainen muu goroutine kuplassa on kestävästi estetty, joten havainnoit järjestelmää sen jälkeen, kun se on tasaantunut.

Aikakatkaisut, uudelleenyritykset, circuit breakerit, nopeusrajoittimet — jokainen omistamasi aikaan perustuva HTTP-asiakastesti voi muuttua nopeaksi ja deterministiseksi. Jos testipakettisi on tällä hetkellä koossa time.Sleep(100 * time.Millisecond)-kutsulla ja toivolla, tämä on poistumistiesi.

3.7. net/http-muutokset, jotka todella vaikuttavat tuotantoon

Hiljaisia, mutta ne näkyvät metriikoissasi.

HTTP/1-vastausrunkojen automaattinen tyhjennys Close-kutsussa. Lukematon sisältö tyhjennetään nyt (konservatiiviseen raja-arvoon 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 mitätön muutos tai pieni voitto. Jos se tekee asioista huonompia, olet todennäköisesti siinä kategoriassa, jonka julkaisutiedot kuvailevat kohteliaasti: Transport.MaxIdleConns asetettu arvoon 0 tai uusi Client per pyyntö, mikä ohittaa tyhjäkäyntiyhteyksien rajan kokonaan. Transport.DisableKeepAlives = true korjaa sen, mutta julkaisutietojen varsinainen neuvo on, että "syvällisempi tarkastelu olisi todennäköisesti hyödyllistä", mikä on Go-tiimin kieltä sille, että sinulla on suurempia ongelmia.

HTTP/2-asiakaspriorisointi (RFC 9218). Palvelin kunnioittaa nyt asiakkaan prioriteettisignaaleja. Jos pidit vanhasta round-robin-aikataulutuksesta, käytä Server.DisableClientPriority = true.

Server.MaxHeaderValueCount. Rajoittaa, kuinka monta otsikkoarvoa palvelin hyväksyy, oletusarvona DefaultMaxHeaderValueCount. Yksi ovi lisää kiinni "lähetä kymmenentuhatta otsikkoa ja katso mitä tapahtuu" -hyökkäyksille.

ALPN käyttäjän tarjoamissa yhteyksissä. Jos net.Conn-yhteytesi toteuttaa ConnectionState() tls.ConnectionState, Transport ja Server tekevät TLS ALPN-neuvottelun sen päällä — joten välityspalvelimen läpi kulkevat mukautetut dialerit voivat yhä neuvotella HTTP/2-yhteyden.

3.8. Pienet asiat, jotka tuottavat iloa

strings.CutLast / bytes.CutLast. Cut pilkkoo ensimmäisen erottimen kohdalta. Ei ollut "viimeistä" varianttia, joten kaikki kirjoittivat käsin LastIndex ja viipaloinnin, ja noin 30 % meistä teki off-by-one-virheen ensimmäisellä yrittämällä.

1name, ext, ok := strings.CutLast("archive.tar.gz", ".")
2// archive.tar gz true

Erinomainen tiedostopäätteille ja host:port-osoitteiden jäsentämiseen — missä sinun on löydettävä viimeinen kaksoispiste, koska IPv6-osoitteet ovat niitä täynnä.

math/big.Int.Divide. Jako eksplisiittisellä pyöristystilalla, korvaten "onko se Quo vai Div mitä haluan?" -kolikonheiton:

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, jossa pyöristyssääntö on kirjoitettu säädöksiin, tämä on sinulle.

url.URL.Clone ja url.Values.Clone. Syväkopiot. Vanha *u-pintakopio jakoi Userinfo-osoittimen, mikä tuotti juuri sellaisen bugin, jonka löytämiseen menee päivä ja korjaamiseen 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-lukumetodit palauttavat nyt suoraan io.EOF-virheen sen sijaan, että ne käärisivät sen net.OpError-virheeseen. 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 suorittaa; toinen antaa ajureiden skannata suoraan käyttäjän kohteisiin, ohittaen väliallokoinnin.

unicode 15 → 17. Kaksi versiota yhdellä hypyllä. Merkkijonojen luokittelu ja normalisointikäyttäytyminen voivat muuttua hienovaraisesti. Jos sinulla on testejä, jotka riippuvat tästä, tulet huomaamaan sen.

compress/flate nopeutui — ja sen tuottamat tavut voivat poiketa Go 1.26:sta. Tämä heijastuu archive/zip-, compress/gzip-, compress/zlib- ja image/png-paketteihin. Jos sinulla on kulta-tiedostotestejä (golden-file tests), jotka hash-arvottavat pakattua tulostetta, ne tulevat rikkoutumaan, eikä se ole regressio. Tarkista tämä ennen kuin päivität, ei vasta tapauksen tutkinnan aikana.

4. Työkaluketju

4.1. go fix sai lisää modernisoijia

go fix on hiljaa muuttumassa koodin modernisointityökaluksi. Go 1.27 lisää atomictypes-, embedlit-, slicesbackward- ja unsafefuncs-työkalut. Käytä -diff-lippua esikatseluun:

 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 yhteen pieneen tiedostoon:

  • embedlit — kirjoittaa upotetut literaalit uudelleen käyttäen uutta syntaksia kohdasta §1.2
  • atomictypesatomic.AddInt64(&x, 1) muuttuu muotoon atomic.Int64.Add(1), mikä tekee ei-atomisesta pääsystä mahdotonta ja korjaa hiljaisesti 32-bittisiä kohdistusvirheitä, joiden olemassaolosta et tiennyt
  • slicesbackward — takaperin menevät silmukat muuttuvat slices.Backward-muotoon
  • waitgroupgoAdd/go/Done-rituaali muuttuu muotoon wg.Go (uudelleennimetty 1.26:n waitgroup-nimestä sekaannusten välttämiseksi)

go tool fix help listaa kaikki 26; go tool fix help <name> selittää yhden. Myös fmtappendf poistettiin "tyylillisten huolien vuoksi", mikä on kauniin diplomaattinen tapa kuvailla sitä, mitä kyseisessä keskusteluketjussa tapahtui.

go fix ./...-komennon ajaminen vanhaan koodikantaan on aidosti tyydyttävä iltapäivä. Lue diff ensin, tietysti.

4.2. go test ajaa oletuksena stdversion-tarkistuksen

Tämä on muutos, joka todennäköisesti keskeyttää päiväsi.

go test ajaa nyt stdversion-vet-tarkistuksen oletuksena, liputtaen standardikirjaston symboleja, jotka ovat uudempia kuin mitä go.mod-tiedostosi 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 epäonnistumismuodon, jossa kaikki toimii koneellasi (uusin työkaluketju) ja räjähtää CI-ympäristö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 sisältävät nyt valinnaisen "OutputType"-kentän: "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-tulosteet (ei kenttää), t.Error-tulosteet (error) ja viitekehyksen luomat rivit (frame) ovat nyt erotettavissa. Jokainen, joka on kirjoittanut regexin selvittääkseen, mitkä testitulosteen rivit ovat "oikeita", voi nyt poistaa sen.

4.4. go doc -parannukset

package@version-syntaksi. Lue dokumentaatio tietylle versiolle 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, mikä muuttui ennen päivitystä.

-ex ja esimerkkikoodin 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

Esimerkkikoodi ja odotettu tuloste, terminaalissa, avaamatta selainvälilehteä, joka on auki vielä ensi torstainakin.

4.5. go mod tidy siivoaa require-lohkot

Moduuleille, jotka käyttävät go 1.27-versiota tai uudempaa, 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// 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// Jälkeen: 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)

Huomaa, että // networking-kommentti säilyi. Tämä siivoaa pääasiassa Git-merge-ristiriitojen ratkaisun jälkiä, missä irralliset require-lohkot syntyvät. Jos tiimissäsi on enemmän kuin kolme ihmistä lisäämässä riippuvuuksia, go.mod-ristiriitosi tulevat olemaan huomattavasti hiljaisempia.

4.6. Muuta pientä

  • bzr-tuki poistettu. Jos tämä vaikuttaa sinuun, haluaisin aidosti kuulla tarinan.
  • go tool trace -http sitoutuu localhost-osoitteeseen, kun sille annetaan vain portti. -http=:6060 ei enää kuuntele jokaista rajapintaa. Käytä -http=0.0.0.0:6060, jos tarkoitit sitä. Tämä vastaa nyt go tool pprof-työkalua ja estää satunnaiset julkiset profiler-yhteydet.
  • Vastaustiedostot (@file) ovat tuettuja compile-, link-, asm-, cgo-, cover- ja pack-komennoissa GCC-yhteensopivassa muodossa. Build-järjestelmille, jotka ylittävät komentorivin pituusrajoitukset — hei, Bazel.

5. Kääntäjä, linkkeri, portit

Kääntäjä. Suhteelliset tiedostonimet //line-direktiiveissä ratkaistaan nyt sisältävän tiedoston hakemiston perusteella, vastaten go/scanner-pakettia. Relevanttia, jos kirjoitat koodigeneraattoreita.

Funktioliteraalien (sulkeumien) symbolinimet ovat nyt myös yksinkertaisempia — sama nimi inlining-toiminnosta riippumatta, ja saman literaalin useat instanssit voivat jakaa koodin binäärissä. Ei toiminnallista muutosta, paitsi: koodi, joka vertailee funktioiden identiteettiä reflect.Value.Pointer-metodilla, näkee "yhtäsuuruuden" useammin kuin ennen. Vertailu ei ollut koskaan validi, mutta jos käytät sitä, nyt se alkaa valehdella sinulle kovempaa.

Linkkeri. Uudet -macos- ja -macsdk-optiot asettavat OS- ja SDK-versiot macOS:n LC_BUILD_VERSION-latauskäskyssä.

Portit.

  • Darwin vaatii nyt macOS 13 Venturan tai uudemman, kuten ilmoitettiin 1.26-versiossa. Tarkista CI-ajurisi.
  • PowerPC (GOOS=linux GOARCH=ppc64) vaihtoi ELFv2 ABI-muotoon. Vaatii Linux-ytimen 3.13+ (RHEL7 backportattu 3.10-versioon). Cgo, PIE ja ulkoinen linkitys ovat nyt tuettuja. Jos käytät cgo-ominaisuutta mutta tarvitset staattisen puhtaan Go-binäärin, aseta CGO_ENABLED=0.

6. Päivityksen tarkistuslista

Go 1.27 ottaa yhteensopivuuden vakavasti, mutta tarkista nämä ennen kuin nostat versiota:

  • Kulta-tiedostotestit pakatulla tulosteella. compress/flate muutti enkoodereita; gzip/zip/png-tavut voivat poiketa.
  • Testit, jotka täsmäyttävät JSON-virheilmoitusten merkkijonoja. Käyttäytyminen on identtistä; virheteksti ei ole.
  • Poistetut GODEBUG-asetukset go.mod-tiedostossa. asynctimerchan, gotypesalias, tlsrsakex, tls3des, tls10server, tlsunsafeekm, x509keypairleaf — nämä epäonnistuvat buildissa vain, jos ne on asetettu vanhoihin arvoihinsa.
  • stdversion-rikkomukset. go test havaitsee nämä nyt. Aja se ajoissa, jos ylläpidät kirjastoa konservatiivisella go-direktiivillä.
  • macOS 12 tai vanhemmat CI-ajurit. Tuki on poissa.
  • Testit, jotka väittävät funktioliteraalien symbolinimistä, ja reflect.Value.Pointer-funktiovertailut.
  • Transport.MaxIdleConns = 0 tai Client per pyyntö. Yhdistettynä automaattisesti tyhjeneviin vastausrunkoihin tämä voi hidastua.

Loppuajatuksia

Go 1.27 on julkaisu, jossa paljon lykättyä ylläpitoa tuli maksettavaksi kerralla.

Geneeriset metodit olivat puuttuva palanen 1.18-version geneerisyystyöstä. Struct-literaalien kenttävalitsimet sulkivat yksitoista vuotta vanhan ongelman. encoding/json/v2 oli kaksi vuotta kestänyt ehdotuskeskustelu. Ja uuid täytti aukon, jota käytännössä jokainen Go-projekti oli paikkaillut samalla kolmannen osapuolen riippuvuudella vuosikymmenen ajan.

Jos haluat prioriteettijärjestyksen hyödyn saamiseksi tästä versiosta:

  1. httptest.NewTestServer + synctest.Sleep — aikaan perustuvat HTTP-testit muuttuvat nopeiksi ja deterministisiksi. Paras panos-tuotto-suhde koko julkaisussa.
  2. Goroutine-vuotoprofiili — altista yksi päätepiste staging-ympäristössä ja valmistaudu nöyrtymään.
  3. Jäljityksen goroutine-tunnisteet — kolme riviä pprof.Do-koodia pyyntösi sisääntulopisteessä muuttaa seuraavan aamuyön stack dumpin luettavaan muotoon.
  4. go fix ./... — anna työkalun tehdä modernisointi.
  5. encoding/json/v2 — ei kiirettä. Ota käyttöön yksi optio kerrallaan.

Ota go1.27rc3 ja aja testipakettisi sitä vasten nyt. Kaikki tuolla tarkistuslistalla on merkittävästi miellyttävämpää löytää tiistai-iltapäivänä kuin tapauksen aikana.

Viitteet