---
id: 1a31e06c0d969b4b6cbbc590259cf089
author: iwanhae
title: Go 1.27, eli miten opin lakkaamaan huolehtimasta ja rakastamaan geneerisiä metodeja
description: Geneeriset metodit, encoding/json/v2, vakiomuotoinen uuid-paketti sekä goroutine-vuotojen tunnistin, joka todella toimii. Katsaus Go 1.27:ään esimerkkeineen, jotka kaikki on todella ajettu.
language: fi
date: 2026-08-15T02:08:43.129534158Z
path: /blog/posts/go-1-27-or-how-i-learned-to-stop-worrying-and-love-generic-methods-za800e454
category: release
---

![Yleiskatsaus Go 1.27:stä](/assets/images/whats-new-in-go1.27/overview.webp)

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.

```bash
go install golang.org/dl/go1.27rc3@latest
go1.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:

```go
type List[E any] []E

// F on metodin itsensä määrittelemä tyyppiparametri
func (l List[E]) Apply[F any](f func(E) F) List[F] {
	r := make(List[F], len(l))
	for i, x := range l {
		r[i] = f(x)
	}
	return r
}

func main() {
	l := List[int]{1, 2, 3}
	fmt.Println(l.Apply(func(i int) string { return fmt.Sprint(i * 10) }))
	// [10 20 30]
}
```

Vastaanottajan ei tarvitse edes olla geneerinen. Tavallinen struct voi saada geneerisen metodin:

```go
type Bag struct{ items []any }

func (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:

1. Rajapintametodit eivät voi määritellä tyyppiparametreja.
2. Geneeriset metodit eivät voi toteuttaa rajapintametodeja.

Tuo jälkimmäinen on se, joka kostautuu:

```go
type Adder interface{ Add(int) int }

type G struct{}

func (G) Add[T any](v T) T { return v }

var _ Adder = G{}
```

```
cannot use G{} (value of struct type G) as Adder value in variable declaration:
	G does not implement Adder (wrong type for method Add)
		have Add[T any](T) T
		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:

```go
func (r *Rand) N[Int intType](n Int) Int
```

```go
r := rand.New(rand.NewPCG(1, 2))
fmt.Println(r.N(int32(100)))     // 76
fmt.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](https://go.dev/issue/9859) avattiin vuonna 2015. Se on nyt suljettu. Jossain päin maailmaa gopher, joka on sittemmin vaihtanut uraa kahdesti, saa GitHub-ilmoituksen.

```go
type Object struct{ name, color string }
type Point3D struct {
	Object
	x, y, z float64
}
type Line struct {
	Object
	p, q Point3D
}

// Go 1.27: käytä nostettua kenttää suoraan
line := 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.

```go
_ = Line{Object.name: "x"}   // invalid field name Object.name in struct literal
_ = 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ää.**

```go
obj := Object{"edge", "black"}
_ = Line{Object: obj, name: "diagonal"}
// 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.**

```go
type PtrEmbed struct {
	*Object
	z int
}

_ = PtrEmbed{name: "x"}
// 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**:

```go
func double[T ~int | ~float64](v T) T { return v * 2 }

type S struct{ f func(int) int }
type A [1]func(int) int

func main() {
	s := S{f: double}          // 1.26: tarvittiin double[int]
	a := A{double}             // 1.26: tarvittiin double[int]

	c := make(chan func(int) int, 1)
	c <- double                // 1.26: tarvittiin double[int]

	var fn func(float64) float64 = double  // tämä toimi aina

	fmt.Println(s.f(21), a[0](5), (<-c)(7), fn(1.5))
	// 42 10 14 3
}
```

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:

```go
type small struct{ a, b, c, d int }

var sink any

func BenchmarkAlloc(b *testing.B) {
	for b.Loop() {
		sink = &small{}
	}
}

func BenchmarkSlice(b *testing.B) {
	for b.Loop() {
		sink = make([]byte, 48)
	}
}
```

```
# oletus (kokoon erikoistunut malloc päällä)
BenchmarkAlloc-12     3000000     5.358 ns/op
BenchmarkSlice-12     3000000    13.58 ns/op

# GOEXPERIMENT=nosizespecializedmalloc
BenchmarkAlloc-12     3000000     8.722 ns/op
BenchmarkSlice-12     3000000    20.81 ns/op

```

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:

```
2413202 bytes  (oletus)
2361826 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.

```go
func leak() {
	ch := make(chan int) // kukaan ei koskaan lähetä tänne. ei kukaan.
	go func() {
		<-ch
	}()
}

func main() {
	for range 3 {
		leak()
	}
	runtime.GC()
	runtime.GC()
	pprof.Lookup("goroutineleak").WriteTo(os.Stdout, 1)
}
```

```
goroutineleak profile: total 3
3 @ 0x104de3f18 0x104d7f0b0 0x104d7ec34 0x104e374f4 0x104dea024
#	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.

```go
func handle(ctx context.Context) {
	var wg sync.WaitGroup
	wg.Go(func() {
		buf := make([]byte, 4<<10)
		os.Stdout.Write(buf[:runtime.Stack(buf, true)])
	})
	wg.Wait()
}

func main() {
	labels := pprof.Labels("request", "GET /orders/42", "tenant", "acme")
	pprof.Do(context.Background(), labels, handle)
}
```

```
goroutine 3 [running] {request: "GET /orders/42", tenant: acme}:
main.handle.func1()
	/tmp/go127/tblabel/main.go:15 +0x40
sync.(*WaitGroup).Go.func1()
	...
goroutine 1 [sync.WaitGroup.Wait] {request: "GET /orders/42", tenant: acme}:
...
```

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:

```
# go.mod sisältää: godebug asynctimerchan=1
go: error loading go.mod:
go.mod:5: removed GODEBUG "asynctimerchan" set to old value "1" (https://go.dev/doc/godebug#go-127)

# go.mod sisältää: godebug asynctimerchan=0
(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](https://go.dev/issue/71497) 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:

```go
// Go 1.27:n encoding/json.Unmarshal, lyhennettynä
func Unmarshal(data []byte, v any) error {
	return jsonv2.Unmarshal(data, v, DefaultOptionsV1())
}
```

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:

```go
var m map[string]int
json.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ä:**

```go
var m map[string]int
jsonv2.Unmarshal([]byte(`{"a":1,"a":2}`), &m)
// jsontext: duplicate object member name "a"

var s string
jsonv2.Unmarshal([]byte("\"\xff\""), &s)
// 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ä:

```go
type Config struct {
	Name    string   `json:"name"`
	Tags    []string `json:"tags,omitzero"`
	Timeout int      `json:"timeout,omitzero"`
}

out, _ := jsonv2.Marshal(Config{Name: "api"})
// {"name":"api"}

// lajitellut map-avaimet + sisennys
out, _ = jsonv2.Marshal(map[string]int{"b": 2, "a": 1},
	jsonv2.Deterministic(true), jsontext.WithIndent("  "))
// {
//   "a": 1,
//   "b": 2
// }

// hylkää tuntemattomat kentät — aiemmin tarvittiin Decoder ja DisallowUnknownFields()
var c Config
err := jsonv2.Unmarshal([]byte(`{"name":"api","nope":1}`), &c,
	jsonv2.RejectUnknownMembers(true))
// json: cannot unmarshal JSON string into Go main.Config:
//   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:

```go
// säilytä v1-semantiikka, mutta hylkää kaksoiskappaleavaimet kuten v2
jsonv2.Unmarshal(data, &v,
	json.DefaultOptionsV1(),
	jsontext.AllowDuplicateNames(false))
// 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.

```go
import "uuid"

func main() {
	fmt.Println(uuid.NewV4())
	// b97aa695-da08-472c-af81-ff088129019f

	// v7: ylimmät 48 bittiä ovat aikaleima, joten nämä lajittuvat aina luontijärjestyksessä
	a, b := uuid.NewV7(), uuid.NewV7()
	fmt.Println(a.Compare(b) < 0) // true

	u, err := uuid.Parse("urn:uuid:0198a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b")
	fmt.Println(u, err)
	// 0198a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b <nil>

	fmt.Println(uuid.Nil(), uuid.Max())
	// 00000000-0000-0000-0000-000000000000 ffffffff-ffff-ffff-ffff-ffffffffffff
}
```

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ää:

- **`UUID` on `[16]byte`**, joten `==` toimii ja sitä voi käyttää suoraan map-avaimena. Sama suunnittelu kuin `google/uuid`:ssä.
- **`Nil` ja `Max` ovat funktioita, eivät muuttujia.** Koska paketin tason `var Nil UUID` on ladattu ase, joka on suunnattu jalkaasi, ja joku jossain lopulta sijoittaisi siihen.
- Satunnaisbitit tulevat **kryptografisesti turvallisesta** generaattorista.
- Se toteuttaa `encoding.TextMarshaler`-, `TextUnmarshaler`- ja `TextAppender`-rajapinnat, joten se sopii suoraan JSON-struct-tyyppeihin.
- **`NewV7` on 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.

```go
sk, _ := mldsa.GenerateKey(mldsa.MLDSA65())
pk := sk.PublicKey()

msg := []byte("release the gophers")
opts := &mldsa.Options{Context: "gosuda.org/blog"}

sig, _ := sk.Sign(rand.Reader, msg, opts)

fmt.Println("private key seed:", len(sk.Bytes()), "bytes")  // 32
fmt.Println("public key:", len(pk.Bytes()), "bytes")        // 1952
fmt.Println("signature:", len(sig), "bytes")                // 3309

fmt.Println(mldsa.Verify(pk, msg, sig, opts))
// <nil>
fmt.Println(mldsa.Verify(pk, msg, sig, &mldsa.Options{Context: "other"}))
// 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`.

```go
// dst = a*x + y
func axpy(dst, x, y []float32, a float32) {
	va := simd.BroadcastFloat32s(a)
	w := va.Len()

	i := 0
	for ; i+w <= len(x); i += w {
		vx := simd.LoadFloat32s(x[i:])
		vy := simd.LoadFloat32s(y[i:])
		vx.MulAdd(va, vy).Store(dst[i:])
	}
	// käsittele häntä osittaisella lataus-/tallennusoperaatiolla — ei skalaarista siivousSilmukkaa
	if i < len(x) {
		vx, _ := simd.LoadFloat32sPart(x[i:])
		vy, _ := simd.LoadFloat32sPart(y[i:])
		vx.MulAdd(va, vy).StorePart(dst[i:])
	}
}
```

```bash
$ GOEXPERIMENT=simd go1.27rc3 run ./simddemo
vector bits: 128 emulated: false
[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:

```go
type Hasher[T any] interface {
	Hash(*Hash, T)
	Equal(x, y T) bool
}
```

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:

```go
type CaseInsensitive struct{}

func (CaseInsensitive) Hash(h *maphash.Hash, s string) {
	h.WriteString(strings.ToLower(s))
}
func (CaseInsensitive) Equal(x, y string) bool {
	return strings.ToLower(x) == strings.ToLower(y)
}

var seed = maphash.MakeSeed()

func hashOf[T any](hr maphash.Hasher[T], v T) uint64 {
	var h maphash.Hash
	h.SetSeed(seed)   // sama siemen, tai tästä ei ole mitään hyötyä
	hr.Hash(&h, v)
	return h.Sum64()
}

fmt.Println(hashOf(CaseInsensitive{}, "Go") == hashOf(CaseInsensitive{}, "GO"))
// true
```

Tavallista `==`-semantiikkaa varten on `ComparableHasher[T]`:

```go
hashOf(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:

```go
func TestRetryWithBackoff(t *testing.T) {
	synctest.Test(t, func(t *testing.T) {
		var hits int
		srv := httptest.NewTestServer(t, http.HandlerFunc(
			func(w http.ResponseWriter, r *http.Request) {
				hits++
				if hits < 3 {
					w.WriteHeader(http.StatusServiceUnavailable)
					return
				}
				io.WriteString(w, "ok")
			}))

		client := srv.Client()
		start := time.Now()
		var body string
		for attempt := range 5 {
			resp, err := client.Get(srv.URL)
			if err != nil {
				t.Fatal(err)
			}
			b, _ := io.ReadAll(resp.Body)
			resp.Body.Close()
			if resp.StatusCode == http.StatusOK {
				body = string(b)
				break
			}
			synctest.Sleep(time.Duration(1<<attempt) * time.Second)
		}

		if body != "ok" {
			t.Fatalf("got %q", body)
		}
		// viiveen tulisi olla täsmälleen 1s + 2s = 3s
		if elapsed := time.Since(start); elapsed != 3*time.Second {
			t.Fatalf("elapsed = %v, want 3s", elapsed)
		}
		t.Logf("hits=%d elapsed=%v", hits, time.Since(start))
	})
}
```

```
=== RUN   TestRetryWithBackoff
    x_test.go:48: hits=3 elapsed=3s
--- 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:

```go
// ei enää tarpeen
defer func() {
	io.Copy(io.Discard, resp.Body)
	resp.Body.Close()
}()
```

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ä.

```go
name, ext, ok := strings.CutLast("archive.tar.gz", ".")
// 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:

```go
x, y := big.NewInt(-7), big.NewInt(2)

new(big.Int).Divide(x, y, new(big.Int), big.Trunc)  // q=-3 r=-1
new(big.Int).Divide(x, y, new(big.Int), big.Floor)  // q=-4 r=1
new(big.Int).Divide(x, y, new(big.Int), big.Round)  // q=-4 r=1
new(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.

```go
orig, _ := url.Parse("https://gosuda.org/blog?tag=go&tag=1.27")
clone := orig.Clone()
clone.Host = "example.com"
fmt.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`:

```go
package fixdemo

import (
	"sync"
	"sync/atomic"
)

type Base struct{ ID int }
type User struct {
	Base
	Name string
}

func New() User {
	return User{Base: Base{ID: 1}, Name: "gopher"}
}

var counter int64

func Incr() { atomic.AddInt64(&counter, 1) }

func Reverse(s []string) {
	for i := len(s) - 1; i >= 0; i-- {
		_ = s[i]
	}
}

func Spawn(wg *sync.WaitGroup) {
	wg.Add(1)
	go func() {
		defer wg.Done()
	}()
}
```

```bash
$ go1.27rc3 fix -diff ./fixdemo
```

```diff
 import (
+	"slices"
 	"sync"
 	"sync/atomic"
 )

 func New() User {
-	return User{Base: Base{ID: 1}, Name: "gopher"}
+	return User{ID: 1, Name: "gopher"}
 }

-var counter int64
+var counter atomic.Int64

-func Incr() { atomic.AddInt64(&counter, 1) }
+func Incr() { counter.Add(1) }

 func Reverse(s []string) {
-	for i := len(s) - 1; i >= 0; i-- {
-		_ = s[i]
+	for _, v := range slices.Backward(s) {
+		_ = v
 	}
 }

 func Spawn(wg *sync.WaitGroup) {
-	wg.Add(1)
-	go func() {
-		defer wg.Done()
-	}()
+	wg.Go(func() {
+	})
 }
```

Neljä modernisoijaa laukesi yhdessä pienessä tiedostossa:

- `embedlit`: kirjoittaa upotetut literaalit uudelleen §1.2:n uudella syntaksilla
- `atomictypes`: `atomic.AddInt64(&x, 1)` muuttuu muotoon `atomic.Int64.Add(1)`, mikä tekee ei-atomisen käytön mahdottomaksi ja korjaa hiljaa 32-bittisen tasauksen virheet, joista et tiennyt
- `slicesbackward`: takaperoiset silmukat muuttuvat muotoon `slices.Backward`
- `waitgroupgo`: `Add`/`go`/`Done`-rituaali muuttuu muotoon `wg.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:

```go
// go.mod sanoo: go 1.24
package sv

import "strings"

func Ext(name string) string {
	_, ext, _ := strings.CutLast(name, ".")  // CutLast on 1.27-symboli
	return ext
}
```

```
$ go1.27rc3 test ./...
# sv
./x.go:6:23: strings.CutLast requires go1.27 or later (module is go1.24)
FAIL	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"`.

```
'frame'  '=== RUN   TestFail\n'
None     '    x_test.go:6: hello\n'
'error'  '    x_test.go:7: boom\n'
'frame'  '--- FAIL: TestFail (0.00s)\n'
'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:

```bash
$ go1.27rc3 doc golang.org/x/sync/errgroup@v0.10.0
package errgroup // import "golang.org/x/sync/errgroup"
...
```

Kätevä tarkistamaan, mitä muuttui ennen päivitystä.

**`-ex` ja esimerkkilähdekoodin tulostus:**

```bash
$ go1.27rc3 doc -ex strings | grep Example
    func ExampleClone()
    func ExampleCompare()
    func ExampleContains()
    func ExampleCut()
    func ExampleCutPrefix()
    ...

$ go1.27rc3 doc strings.ExampleCut
package main

import (
	"fmt"
	"strings"
)

func main() {
	show := func(s, sep string) {
		before, after, found := strings.Cut(s, sep)
		fmt.Printf("Cut(%q, %q) = %q, %q, %v\n", s, sep, before, after, found)
	}
	show("Gopher", "Go")
	...
}

Output:
Cut("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.

```go
// Ennen
module tidydemo

go 1.27

require golang.org/x/sync v0.10.0

// verkko
require golang.org/x/net v0.33.0

require (
	golang.org/x/sys v0.28.0 // indirect
)

require golang.org/x/text v0.21.0 // indirect
```

```go
// Jälkeen: go mod tidy
module tidydemo

go 1.27

require (
	// verkko
	golang.org/x/net v0.33.0
	golang.org/x/sync v0.10.0
)
```

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 -http` sitoutuu localhostiin**, kun annetaan pelkkä portti. `-http=:6060` ei enää kuuntele kaikilla rajapinnoilla. Käytä `-http=0.0.0.0:6060`, jos tarkoitit sitä. Tämä vastaa nyt `go tool pprof`-komentoa ja estää satunnaisen vahingossa julkisen profiloijan.
- **Vastaustiedostot (`@file`)** ovat tuettuja komennoissa `compile`, `link`, `asm`, `cgo`, `cover` ja `pack` GCC-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, aseta `CGO_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/flate` muutti 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 test` havaitsee nämä nyt. Aja se aikaisin, jos ylläpidät kirjastoa konservatiivisella `go`-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 = 0`** tai `Client` jokaista 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ä:

1. **`httptest.NewTestServer` + `synctest.Sleep`**: aikariippuvaisista HTTP-testeistä tulee nopeita ja deterministisiä. Paras panostuksen ja hyödyn suhde koko julkaisussa.
2. **Goroutine-vuotoprofiili**: avaa yksi päätepiste staging-ympäristössä ja valmistaudu nöyrtymään.
3. **Jäljityksen goroutine-tunnisteet**: kolme riviä `pprof.Do`:a pyyntösi sisäänkäyntipisteessä muuttaa seuraavan kello 3 yöllä otettavan pinovedoksen luettavaksi.
4. **`go fix ./...`**: anna työkalun hoitaa modernisointi.
5. **`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**

- [Go 1.27 Release Notes](https://go.dev/doc/go1.27)
- [The Go Programming Language Specification](https://go.dev/ref/spec)
- [proposal: encoding/json/v2 (#71497)](https://go.dev/issue/71497)
- [proposal: portable SIMD (#78902)](https://go.dev/issue/78902)
- [proposal: hash/maphash.Hasher (#70471)](https://go.dev/issue/70471)
