---
id: 1a31e06c0d969b4b6cbbc590259cf089
author: iwanhae
title: Go 1.27, eller Hvordan jeg lærte å slutte å bekymre meg og elske Generic Methods
description: Generiske metoder, encoding/json/v2, en standard uuid-pakke og en goroutine-lekkasjedetektor som faktisk fungerer. En gjennomgang av Go 1.27 med eksempler som alle faktisk er kjørt.
language: "no"
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
---

![Oversikt over Go 1.27](/assets/images/whats-new-in-go1.27/overview.webp)

Go 1.27 kommer i august 2026, og for en gangs skyld har selve språket endret seg. Ikke bare «vi har lagt til en ny funksjon i `slices`», men faktisk endret. Generiske metoder er på plass. En elleve år gammel issue ble lukket. `encoding/json` ble stille erstattet med en ny motor mens flyet var i luften.

Alle eksemplene under ble kjørt på `go1.27rc3` (darwin/arm64). Alle output-blokkene er ekte output, kopiert rett inn, inkludert feilmeldingene. Hvis noe ser merkelig ut, er det fordi det er det kompilatoren faktisk sa.

```bash
go install golang.org/dl/go1.27rc3@latest
go1.27rc3 download
```

## 1. Språket endret seg (ja, virkelig)

### 1.1. Generiske metoder

Siden Go 1.18 har vi hatt generics, og siden Go 1.18 har vi hatt Samtalen. Den foregår slik:

> "La meg bare skrive en `Map`-metode på slice-typen min..."
>
> `method must have no type parameters`
>
> "...greit. Da blir det en funksjon på pakkenivå."

Metoder kunne bare bruke typeparametere deklarert av _receiveren_. Din egen metode kunne ikke introdusere nye. Så hver generisk transformasjon ble forvist til pakkescope, der den sto ved siden av sytten andre frittstående hjelpefunksjoner med navn som er en variant av `MapSlice`.

Go 1.27 fikser dette:

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

// F er en typeparameter deklarert av metoden selv
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]
}
```

Receiveren trenger ikke engang å være generisk. En kjedelig struct kan ha en generisk metode:

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

func (b *Bag) Add[T any](v T) { b.items = append(b.items, v) }
```

Før du refaktorerer hele kodebasen i ettermiddag, finnes det imidlertid **to begrensninger**, og de betyr mer enn de ser ut til:

1. Interface-metoder kan ikke deklarere typeparametere.
2. Generiske metoder kan ikke implementere interface-metoder.

Den andre er den som vil bite deg:

```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
```

Så generiske metoder og dynamisk dispatch passer ikke sammen. Dette er ikke Go-teamet som er gjerrige: en generisk metode har uendelig mange instansieringer, mens en interface-metodetabell må være endelig og kjent ved kompileringstid. Man kan ikke legge noe uendelig i en endelig tabell. Universet sa nei.

Praktisk tolkning: generiske metoder er til for konkrete typer med API-er i form av verktøyfunksjoner. Alt som må skjules bak et interface, trenger fortsatt den gamle tilnærmingen.

Standardbiblioteket har allerede utnyttet dette. `Rand` i `math/rand/v2` fikk en generisk metode:

```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
```

Tidligere var bare den pakkenivå-baserte `rand.N` generisk, noe som innebar at man måtte bruke den globale kilden. Nå får din egen seedede `*Rand` samme bekvemmelighet. En liten ting. En fin ting.

### 1.2. Feltvelgere i struct-literaler, eller: Issue #9859 får endelig hvile

Embedding gir deg `u.ID`. Embedding gir _ikke_ deg `User{ID: 1}`. I stedet gir det deg `User{Base: Base{ID: 1}}`, som er Go sin måte å spørre om du virkelig mente det.

[Issue #9859](https://go.dev/issue/9859) ble opprettet i 2015. Den er nå lukket. Et sted gjør en gopher som siden har byttet karriere to ganger, og som fortsatt har et GitHub-varsel.

```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: bruk det promoterte feltet direkte
line := Line{name: "diagonal", q: Point3D{y: -4, z: 12.3}}
```

`name` er ikke et felt i `Line`. Det er et felt i den innebygde `Object`. Tidligere måtte du skrive `Line{Object: Object{name: "diagonal"}, ...}`.

Nå til det med småskriften, som jeg sjekket ved faktisk å prøve å kompilere alt.

**(a) Nøkkelen er fortsatt en vanlig identifikator.** Du kan ikke skrive en vilkårlig selektorsti.

```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
```

Så funksjonen er ikke «nøkler fungerer nå som feltaksess». Den er spesifikt at implisitt promoterte feltnavn er tillatt. Litt smalere enn overskriften antyder.

**(b) Du kan ikke spesifisere et innebygd felt og noe promotert fra det samtidig.**

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

Rimelig. Du fortalte den to motstridende ting om samme minne, og den valgte å ikke gjette.

**(c) Pekerembedding er ikke velkomment.**

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

_ = PtrEmbed{name: "x"}
// invalid implicit pointer indirection to reach name
```

For å følge en peker trenger du en peker å følge, og på konstruksjonstidspunktet for literalen finnes den ikke ennå. Også rimelig.

Gode nyheter: `go fix` kan skrive om de gamle literalene dine for deg. Mer om det senere.

### 1.3. Typeinferens for funksjonstyper er mindre tilfeldig

Typeinferens for generiske funksjoner fungerte tidligere på noen steder og ikke andre, uten noe synlig prinsipp bak hvilke som var hvilke. Tilordne til en variabel? Fint. Legge den nøyaktig samme funksjonen i et struct-felt? Kompileringsfeil, vennligst skriv `double[int]` som i 2022.

Go 1.27 gjør at inferens fungerer i **alle kontekster der måltypen er entydig kjent**:

```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: krevde double[int]
	a := A{double}             // 1.26: krevde double[int]

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

	var fn func(float64) float64 = double  // dette fungerte alltid

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

Én `double`, instansiert som `func(int) int` på tre steder og `func(float64) float64` på et fjerde, utelukkende ut fra kontekst. Hvis du skriver optionstrukturer eller handler-tabeller fulle av funksjonsverdier, er en hel del `[T]`-støy i ferd med å forsvinne fra kodebasen din.

## 2. Runtime: Gratis ytelse og en lekkasjedetektor

### 2.1. Størrelsesspesialisert allokering

Kompilatoren sender nå kall til størrelsesspesialiserte allokeringsrutiner for små objekter. Release notes hevder opptil 30 % raskere allokeringer under 80 byte, og omtrent 1 % totalt for allokeringstunge programmer.

Jeg tror ikke på release notes uten å måle, så:

```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)
	}
}
```

```
# standard (størrelsesspesialisert malloc på)
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

```

30–35 % raskere i en mikrobenchmark som bare allokerer på Apple Silicon. Det vil si: dette er best case, oppnådd under laboratorieforhold av en benchmark designet for å få tallet til å se bra ut. I et reelt program vil GC og faktisk arbeid begrave mesteparten av det. Påstanden om ~1 % er den ærlige.

Kostnaden er binærstørrelse. Hello World:

```
2413202 bytes  (standard)
2361826 bytes  (nosizespecializedmalloc)
```

Omtrent 50 KB, fast, uavhengig av programmet ditt. Du kan velge bort dette med `GOEXPERIMENT=nosizespecializedmalloc`, men **den nødutgangen er planlagt fjernet i Go 1.28**, så behandle den som en workaround for feilrapporter, ikke som en livsstil.

### 2.2. Goroutine-lekkasjeprofilen er ekte nå

Eksperimentell i Go 1.26, generelt tilgjengelig i 1.27. GOEXPERIMENT-flagget `goroutineleakprofile` er borte; det bare fungerer.

```go
func leak() {
	ch := make(chan int) // ingen vil noensinne sende. ingen.
	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
```

Tre goroutines, permanent fastlåst, med en fil og et linjenummer som peker nøyaktig på hvor du gjorde det.

Triksene bak dette er virkelig smarte: det **gjenbruker garbage collectorens analyse av nåbarhet**. Hvis goroutine G er blokkert på primitiven P, og P ikke kan nås fra noen kjørbar goroutine (eller noe disse goroutinene kunne vekke), så kan ingenting noensinne berøre P igjen, og G våkner derfor aldri. Dette er ikke en heuristikk, det er et bevis.

Det samme designet gir deg begrensningen gratis: hvis kanalen eller mutexen kan nås via en global variabel, eller via en lokal variabel i en goroutine som fortsatt kjører, kan GC fortsatt se den, og runtime kan ikke konkludere noe. Legg merke til at selv det lille eksemplet over trenger to kall til `runtime.GC()` for å rapportere. Den fanger ikke alt.

Den vil imidlertid fange den klassiske «glemte å kansellere konteksten, worker-goroutinen lever evig», som er de fleste lekkasjene de fleste ganger.

Hvis du importerer `net/http/pprof`, finnes den også på `/debug/pprof/goroutineleak`. Koble den til staging, sjekk den ukentlig, og bli stille forferdet.

Dette arbeidet ble bidratt av Vlad Saioc hos Uber, som fortjener en drikke etter eget valg.

### 2.3. Tracebacks forteller nå hvilken forespørsel som døde

For moduler som deklarerer Go 1.27 eller senere, inkluderer traceback-headerlinjer nå goroutine-labels fra `runtime/pprof`.

```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}:
...
```

Det viktige detaljen: **labels arves av barnegoroutiner.** Goroutine 3 satte aldri en label. Den fikk en fra sin forelder.

Så neste gang produksjonen deadlocker og noen sender SIGQUIT til prosessen, får du i stedet for 4 000 goroutines som alle ser like ut, 4 000 goroutines merket med hvilken forespørsel og hvilken tenant de tilhører. Tre linjer med `pprof.Do` ved inngangspunktet for forespørselen din kjøper deg dette.

Siden labels kan inneholde ting du helst ikke vil dumpe til stderr, slår `GODEBUG=tracebacklabels=0` det av, og denne opt-out-en er uttrykkelig ment å bli værende for alltid.

### 2.4. asynctimerchan er borte for godt

Go 1.23 gjorde timerkanaler ubufrede (synkrone) og tilbød `asynctimerchan=1` som en vei tilbake til den gamle oppførselen. I 1.27 er den innstillingen **permanent fjernet**. Kanalene i pakken `time` er synkrone, punktum, ingen forhandling.

Det interessante er policyen som ble introdusert sammen med dette. En fjernet GODEBUG som står igjen i `go.mod` bryter ikke automatisk bygget. Den bryter bare hvis den er satt til den **gamle** verdien:

```
# go.mod inneholder: 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 inneholder: godebug asynctimerchan=0
(bygger fint)
```

Det betyr at de eneste som får kjeft, er de som faktisk var avhengige av den fjernede oppførselen. Alle som satte den til den endelige standardverdien og glemte det, får fortsette å ikke tenke på det. Det er en gjennomtenkt bit av API-arkeologi.

## 3. Standardbiblioteket

### 3.1. encoding/json/v2: De byttet ut motoren underveis

Dette er den store. [Over to års diskusjon om proposal](https://go.dev/issue/71497) landet endelig.

Det finnes nå **tre pakker**, og å forstå oppdelingen er mesteparten av jobben:

| Pakke                    | Oppgave                                                                        |
| ------------------------ | ------------------------------------------------------------------------------ |
| `encoding/json`          | v1-API-et du kjenner. **Oppførselen er 100 % uendret.** Implementert på toppen av v2 |
| `encoding/json/v2`       | Semantisk prosessering. Go-verdier ↔ JSON                                      |
| `encoding/json/jsontext` | Syntaktisk prosessering. JSON som en tokenstrøm                                |

Hovedpoenget er at `encoding/json` ble bygget om på en helt annen implementasjon **og oppfører seg identisk**. Slik:

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

Hver eneste legacy v1-kuriositet ble kodet som en option, samlet i `DefaultOptionsV1()`, og v1-API-et bruker alltid denne pakken. Det betyr at dette oppfører seg likt i 1.26 og 1.27:

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

Ja, v1 aksepterer fortsatt stille dupliserte nøkler og tar den siste. Det har alltid gjort det. Det gjør det fortsatt. Kompatibilitet betyr kompatibilitet også med de dårlige delene.

**v2 har derimot meninger:**

```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
```

Dupliserte nøkler og ugyldig UTF-8 avvises. Begge er virkelig farlige, ikke bare rotete. Når to parsere er uenige om hvilken duplisert nøkkel som vinner, får du sikkerhetsfeil. CouchDBs CVE-2017-12635 var nettopp dette: en JSON-body med to `roles`-nøkler, der valideringen leste den ene og lagringslaget leste den andre. Å avvise er riktig valg.

Options er variadiske:

```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"}

// sorterte map-nøkler + innrykk
out, _ = jsonv2.Marshal(map[string]int{"b": 2, "a": 1},
	jsonv2.Deterministic(true), jsontext.WithIndent("  "))
// {
//   "a": 1,
//   "b": 2
// }

// avvis ukjente felt; tidligere krevde dette en Decoder og 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`: de fleste tingene du tidligere løste med et tredjepartsbibliotek eller en håndskrevet `MarshalJSON`, er nå flagg.

Og migreringshistorien er den beste delen. **Senere options vinner**, så du kan ta i bruk v2 sin strenghet én oppførsel om gangen:

```go
// behold v1-semantikk, men avvis dupliserte nøkler slik v2 gjør
jsonv2.Unmarshal(data, &v,
	json.DefaultOptionsV1(),
	jsontext.AllowDuplicateNames(false))
// duplicate object member name
```

Du trenger ikke å portere en kodebase på 200 000 linjer til v2 for å slutte å akseptere dupliserte nøkler. Du slår av én option. For alt stort er dette den realistiske veien.

Ytelse: marshaling er omtrent på paritet, **unmarshaling er betydelig raskere.** Hvis noe går galt, gjenoppretter `GOEXPERIMENT=nojsonv2` den gamle implementasjonen. Også denne opt-out-en står på huggeblokken etter hvert, så opprett en issue heller enn å slå deg til ro med den.

`jsontext` er det lavnivålaget: `Encoder`/`Decoder` som går gjennom JSON som `Token`s og `Value`s, med en tilstandsmaskin som holder deg ærlig. Ta det i bruk når du skriver en streaming-transformator eller et JSON-filter og ikke vil materialisere Go-verdier i det hele tatt.

### 3.2. En standard uuid-pakke

Endelig. RFC 9562, i standardbiblioteket, ingen `go get` nødvendig.

```go
import "uuid"

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

	// v7: de øverste 48 bitene er en tidsstempel, så disse sorteres alltid i opprettelsesrekkefølge
	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
}
```

Hele API-et er `New`, `NewV4`, `NewV7`, `Nil`, `Max`, `Parse`, `MustParse`, og én type: `type UUID [16]byte`. Det er alt. Du kan lese hele pakkedokumentasjonen mens kaffen avkjøles.

Detaljer verdt å vite:

- **`UUID` er `[16]byte`**, så `==` fungerer, og den kan brukes direkte som map-nøkkel. Samme design som `google/uuid`.
- **`Nil` og `Max` er funksjoner, ikke variabler.** Fordi en pakkenivå-`var Nil UUID` er en ladd pistol rettet mot foten din, og noen, et sted, ville før eller siden tilordne til den.
- Tilfeldige bits kommer fra en **kryptografisk sikker** generator.
- Den implementerer `encoding.TextMarshaler`/`TextUnmarshaler`/`TextAppender`, så den passer rett inn i JSON-structs.
- **`NewV7` er den du vil ha for primærnøkler i databaser.** Tidsordnet betyr at B-tree-indeksen din slutter å fragmentere, noe v4-UUID-er er beryktet for.

Du kan nå fjerne en avhengighet. Hvis du trenger v1/v3/v5 eller mer avanserte parsealternativer, har tredjepartsbibliotekene fortsatt en jobb.

### 3.3. crypto/mldsa: Post-kvante-signaturer, og de er enorme

Go 1.24 ga oss `crypto/mlkem` for post-kvante nøkkelutveksling. Go 1.27 leverer den andre halvdelen: ML-DSA-signaturer, standardisert som 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
```

Se på tallene. **En enkelt signatur er 3 309 byte.** Ed25519 er 64. Det er en økning på 50 ganger, og den offentlige nøkkelen er nesten 2 KB i tillegg. Stapler du noen av disse i en sertifikatkjede, må TLS-handshaken din begynne å ha sin egen MTU-strategi.

Dette er den faktiske prisen for kvanteresistens i dag, og det er grunnen til at ingen bytter alt i morgen. Men det er nå i standardbiblioteket, som er der du vil at det skal være _før_ du trenger det.

`Options.Context` er domeneseparasjon: signer med samme nøkkel for ulike formål, bruk en ulik kontekst for hvert, og en signatur fra én kontekst vil ikke verifiseres i en annen. Eksemplet over viser nettopp dette: samme nøkkel, samme melding, ulik kontekst, avvist.

`PrivateKey` implementerer `crypto.Signer`, så den passer inn i eksisterende interfaces. `crypto/x509` håndterer ML-DSA-nøkler og signaturer, og `crypto/tls` støtter signaturskjemaene `MLDSA44`/`MLDSA65`/`MLDSA87` i TLS 1.3.

Det finnes også `SignDeterministic`, som hopper over tilfeldigheten. Nyttig for tester og reproduserbare bygg.

### 3.4. simd: Vektorinstruksjoner uten assembly

Go 1.26 introduserte den arkitekturspesifikke `simd/archsimd` som et eksperiment. Go 1.27 legger til `simd`, som er **portabel og uavhengig av vektorbredde**. Aktiveres med `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:])
	}
	// håndter halen med en delvis load/store, uten skalar opprydningsløkke
	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]
```

Designvalget som betyr noe: **vektorbredden er aldri hardkodet.** Du spør `va.Len()` ved kjøretid. `VectorBitSize()` forteller deg den faktiske bredden, og `Emulated()` forteller om du fikk ekte maskinvare eller en høflig software-etterligning. Min Mac rapporterte 128-bit NEON; en AVX-512-maskin rapporterer mer; en maskin uten noe rapporterer emulering, og koden kjører fortsatt.

`LoadFloat32sPart`/`StorePart` fortjener spesiell omtale. Det mest irriterende med håndskrevet SIMD er alltid den ujevne halen på slutten av arrayet, og dette håndterer den uten en egen skalar løkke.

Fortsatt eksperimentell, API-et er fortsatt ustabilt, ikke sett det i produksjon. Men at dette er Go-kode og ikke assembly eller cgo, er en virkelig stor sak.

### 3.5. hash/maphash.Hasher

Et nytt interface som beskriver kontrakten mellom en verditype og hash-baserte containere:

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

Hvorfor? Gos innebygde map godtar bare `comparable`-nøkler. Du kan ikke bruke en slice som nøkkel. Du kan ikke definere «lik ignorert store/små bokstaver». `Hasher` løser begge samtidig:

```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)   // samme seed, ellers betyr ingenting av dette noe
	hr.Hash(&h, v)
	return h.Sum64()
}

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

For vanlig `==`-semantikk finnes `ComparableHasher[T]`:

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

Fellen: **`Hasher` er et interface, ikke en datastruktur.** Det finnes ingen standard hashtabell eller Bloom-filter som bruker det ennå. Dette er grunnarbeid for en fremtidig containerpakke. I dag bruker du det når du bygger din egen struktur, eller konsumerer en eksisterende implementasjon som `go/types.Hasher` (som lar deg bruke `types.Type` som map-nøkkel, med respekt for `Identical`).

Seed-håndtering er ditt ansvar. Hvis `hashOf` over lagde en ny seed hver gang den ble kalt, ville identiske verdier gitt ulike hasher, og eksemplet ville skrevet ut `false`. Det er nøyaktig feilen jeg skrev i mitt første forsøk. Én seed per container. (Seeden er randomisert for å motvirke hash-flooding-DoS-angrep, og det er grunnen til at den ikke bare er en konstant.)

### 3.6. httptest.NewTestServer + synctest.Sleep: Sleeper-treffet

Hvis du bare tar i bruk én ting fra denne utgivelsen, gjør det til denne.

`testing/synctest` ble stabil i Go 1.25 og ga oss en falsk klokke for testing av samtidig kode. Men så snart testen din berørte et reelt nettverk, falt illusjonen sammen. `httptest.NewTestServer` i Go 1.27 bruker et **nettverk i minnet**, så boblen forblir intakt. Og `synctest.Sleep` (= `time.Sleep` + `synctest.Wait`) fullfører bildet.

Her er en test for retry med eksponentiell backoff:

```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)
		}
		// backoff skal være nøyaktig 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)
```

Les det to ganger. Den simulerte tre sekunders backoff mot en ekte HTTP-server, og var ferdig på **0,00 sekunder**. Og påstanden er `elapsed == 3*time.Second`, _nøyaktig_ tre sekunder, ikke «minst tre sekunder, gitt eller tatt scheduler-jitter». Falske klokker har ingen jitter.

`synctest.Sleep` finnes av en spesifikk grunn: hvis testen din sover like lenge som koden som testes, er det noens gjetning hvilken som våkner først. `synctest.Sleep` sover _og venter deretter_ til alle andre goroutines i boblen er varig blokkert, slik at du observerer systemet etter at det har satt seg.

Timeouts, retries, circuit breakers, rate limiters: hver tidsavhengige HTTP-klienttest du eier, kan bli rask og deterministisk. Hvis testsuiten din i dag holdes sammen med `time.Sleep(100 * time.Millisecond)` og håp, er dette utveien din.

### 3.7. Endringer i net/http som faktisk påvirker produksjon

Stille, men de vil dukke opp i metrikkene dine.

**HTTP/1-responsebodies tømmes automatisk ved Close.** Uleste data tømmes nå (opp til en konservativ grense) når du lukker bodyen, slik at forbindelsen kan gjenbrukes. Det betyr at du endelig kan slette denne besvergelsen som alle har kopiert fra samme Stack Overflow-svar siden 2016:

```go
// er ikke lenger nødvendig
defer func() {
	io.Copy(io.Discard, resp.Body)
	resp.Body.Close()
}()
```

For de fleste programmer er dette en no-op eller en liten gevinst. Hvis det gjør ting _verre_, er du sannsynligvis i bøtta release notes beskriver høflig: `Transport.MaxIdleConns` satt til 0, eller en ny `Client` per forespørsel, som omgår grensen for inaktive forbindelser fullstendig. `Transport.DisableKeepAlives = true` vil tildekke problemet, men release notes sitt faktiske råd er at «a deeper look would likely be beneficial», som på Go-teamspråk betyr _du har større problemer_.

**HTTP/2-klientprioritet (RFC 9218).** Serveren respekterer nå klientens prioritetssignaler. Hvis du foretrakk den gamle round-robin-planleggingen, sett `Server.DisableClientPriority = true`.

**`Server.MaxHeaderValueCount`.** Begrenser hvor mange headerverdier serveren aksepterer, med standard `DefaultMaxHeaderValueCount`. Enda en dør lukket for «send ti tusen headere og se hva som skjer».

**ALPN på brukerleverte forbindelser.** Hvis din `net.Conn` implementerer `ConnectionState() tls.ConnectionState`, vil `Transport` og `Server` utføre TLS ALPN-forhandling på den, slik at egendefinerte dialere som går via en proxy fortsatt kan forhandle frem HTTP/2.

### 3.8. Det lille som gir glede

**`strings.CutLast` / `bytes.CutLast`.** `Cut` deler ved det _første_ skilletegnet. Det fantes ingen «last»-variant, så alle skrev `LastIndex` og slicing for hånd, og omtrent 30 % av oss fikk en off-by-one første gang.

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

Flott for filendelser og for parsing av `host:port`, der du må finne det _siste_ kolonet, fordi IPv6-adresser er fulle av dem.

**`math/big.Int.Divide`.** Divisjon med en eksplisitt avrundingsmodus, som erstatter spørsmålet «er det `Quo` eller `Div` jeg vil ha?»:

```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
```

Hvis du jobber i et domene der avrundingsregelen står skrevet i en forskrift, er dette for deg.

**`url.URL.Clone` og `url.Values.Clone`.** Dype kopier. Det gamle grunne kopieringen med `*u` delte `Userinfo`-pekeren, noe som ga nøyaktig den typen feil som tar en dag å finne og fem sekunder å fikse.

```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
```

**Lesemetodene til `net.UnixConn`** returnerer nå `io.EOF` direkte i stedet for å pakke den inn i `net.OpError`. `err == io.EOF` fungerer nå, slik det alltid burde ha gjort.

**`database/sql.ConvertAssign` og `driver.RowsColumnScanner`.** Funksjoner for driver-forfattere. Den første eksponerer typekonverteringene som `Rows.Scan` utfører; den andre lar drivere skanne direkte inn i brukerdestinasjoner, og hopper over en mellomliggende allokering.

**`unicode` 15 → 17.** To versjoner i ett hopp. Oppførselen for strengklassifisering og normalisering kan endre seg subtilt. Hvis du har tester som avhenger av det, vil du finne det ut.

**`compress/flate` ble raskere**, og **utdata-bytene kan avvike fra Go 1.26.** Dette slår ut i `archive/zip`, `compress/gzip`, `compress/zlib` og `image/png`. Hvis du har golden-file-tester som hasher komprimert utdata, vil de brekke, og det vil ikke være en regresjon. Sjekk dette _før_ du oppgraderer, ikke under gjennomgangen etter hendelsen.

## 4. Verktøykjede

### 4.1. go fix fikk flere modernizere

`go fix` blir stille til et verktøy for kodemodernisering. Go 1.27 legger til `atomictypes`, `embedlit`, `slicesbackward` og `unsafefuncs`. Bruk `-diff` for å forhåndsvise:

```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() {
+	})
 }
```

Fire modernizere slo ut på én liten fil:

- `embedlit`: skriver om innebygde literaler til den nye syntaksen fra §1.2
- `atomictypes`: `atomic.AddInt64(&x, 1)` blir `atomic.Int64.Add(1)`, som gjør ikke-atomisk tilgang umulig og stille retter 32-bit-justeringsfeil du ikke visste at du hadde
- `slicesbackward`: baklengs løkker blir `slices.Backward`
- `waitgroupgo`: ritualet `Add`/`go`/`Done` blir `wg.Go` (omdøpt fra `waitgroup` i 1.26 for å unngå tvetydighet)

`go tool fix help` lister alle 26; `go tool fix help <name>` forklarer én. Dessuten ble `fmtappendf` fjernet «av stilistiske hensyn», som er en vakkert diplomatisk måte å beskrive det som skjedde i den issue-tråden.

Å kjøre `go fix ./...` på en gammel kodebase er en virkelig tilfredsstillende ettermiddag. Les diffen først, selvfølgelig.

### 4.2. go test kjører stdversion som standard

Dette er endringen som mest sannsynlig avbryter dagen din.

`go test` kjører nå vet-sjekken `stdversion` som standard, og flagger standardbibliotek-symboler som er nyere enn det `go`-direktivet i `go.mod` tillater:

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

import "strings"

func Ext(name string) string {
	_, ext, _ := strings.CutLast(name, ".")  // CutLast er et 1.27-symbol
	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]
```

Dette eliminerer den klassiske feilen der alt fungerer på maskinen din (nyeste toolchain) og eksploderer i CI eller i en brukers eldre miljø. Hvis du publiserer biblioteker med et konservativt `go`-direktiv, er dette en gave.

### 4.3. go test -json fikk OutputType

Linjer med `"Action":"output"` har nå et valgfritt felt `"OutputType"`: `"error"`, `"error-continue"` eller `"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'
```

Output fra `t.Log` (uten felt), output fra `t.Error` (`error`) og rammeverkgenererte linjer (`frame`) kan nå skilles fra hverandre. Alle som har skrevet et regulært uttrykk for å finne ut hvilke linjer i testoutput som er «ekte», kan nå slette det.

### 4.4. Forbedringer i go doc

**Syntaksen `package@version`.** Les dokumentasjonen for en bestemt versjon uten å legge den til i modulen din:

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

Praktisk for å sjekke hva som er endret før en oppgradering.

**`-ex` og utskrift av eksempelkilde:**

```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
```

Eksempelkilde _og_ forventet output, i terminalen, uten å åpne en nettleserfane som fortsatt vil være åpen neste torsdag.

### 4.5. go mod tidy rydder i require-blokkene dine

For moduler med `go 1.27` eller nyere slår `go mod tidy` sammen spredte `require`-blokker til maksimalt to (direkte og indirekte), og bevarer tilknyttede kommentarer.

```go
// Før
module tidydemo

go 1.27

require golang.org/x/sync v0.10.0

// nettverk
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
// Etter: go mod tidy
module tidydemo

go 1.27

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

Merk at kommentaren `// nettverk` overlevde. Dette rydder mest opp etter **Git-merge-konflikter**, som er der løse `require`-blokker oppstår. Hvis teamet ditt er mer enn tre personer som legger til avhengigheter, vil konfliktene i `go.mod` bli merkbart roligere.

### 4.6. Småting

- **Støtte for `bzr` er fjernet.** Hvis dette påvirker deg, vil jeg virkelig gjerne høre historien.
- **`go tool trace -http` binder til localhost** når det bare gis en port. `-http=:6060` lytter ikke lenger på alle grensesnitt. Bruk `-http=0.0.0.0:6060` hvis du mente det. Dette samsvarer nå med `go tool pprof`, og forhindrer den sporadiske utilsiktede offentlige profileringstjenesten.
- **Responsfiler (`@file`)** støttes av `compile`, `link`, `asm`, `cgo`, `cover` og `pack`, i et GCC-kompatibelt format. For byggesystemer som sprenger grensene for kommandolinjelengde: hei, Bazel.

## 5. Kompilator, linker, porter

**Kompilator.** Relative filnavn i `//line`-direktiver løses nå mot katalogen til den inneholdende filen, i samsvar med `go/scanner`. Relevant hvis du skriver kodegeneratorer.

Symbolnavn for funksjonsliteraler (closures) er også enklere nå: det samme navnet uavhengig av inlining, og flere forekomster av samme literal kan dele kode i binæren. Ingen funksjonell endring, _bortsett fra_: kode som sammenligner funksjonsidentitet via `reflect.Value.Pointer` vil se «lik» oftere enn før. Den sammenligningen har aldri vært gyldig, men hvis du har den, er nå tiden da den begynner å lyve høyere.

**Linker.** Nye alternativer `-macos` og `-macsdk` setter OS- og SDK-versjonene i `LC_BUILD_VERSION`-lastekommandoen i macOS.

**Porter.**

- **Darwin krever nå macOS 13 Ventura eller nyere**, som annonsert i 1.26. Sjekk CI-runnerne dine.
- **PowerPC (`GOOS=linux GOARCH=ppc64`) gikk over til ELFv2-ABI-en.** Krever Linux-kjerne 3.13+ (RHEL7 har portert tilbake til 3.10). Cgo, PIE og ekstern linking støttes nå. Hvis du bruker cgo, men trenger en statisk ren Go-binær, sett `CGO_ENABLED=0`.

## 6. Oppgraderingssjekkliste

Go 1.27 tar kompatibilitet på alvor, men sjekk disse før du bumper versjonen:

- [ ] **Golden-file-tester på komprimert output.** `compress/flate` endret enkodere; gzip-/zip-/png-bytes kan avvike.
- [ ] **Tester som matcher JSON-feilmeldingstekster.** Oppførselen er identisk; **feilteksten er ikke det.**
- [ ] **Fjernede GODEBUG-er i `go.mod`.** `asynctimerchan`, `gotypesalias`, `tlsrsakex`, `tls3des`, `tls10server`, `tlsunsafeekm`, `x509keypairleaf`: disse feiler bygget bare hvis de er satt til sine _gamle_ verdier.
- [ ] **`stdversion`-brudd.** `go test` fanger disse nå. Kjør det tidlig hvis du vedlikeholder et bibliotek med et konservativt `go`-direktiv.
- [ ] **CI-runnere med macOS 12 eller eldre.** Støtten er borte.
- [ ] **Tester som påstår noe om symbolnavn for funksjonsliteraler**, og **sammenligninger av funksjoner med `reflect.Value.Pointer`.**
- [ ] **`Transport.MaxIdleConns = 0`** eller en `Client` per forespørsel. Kombinert med automatisk tømming av responsebodies kan dette bli tregere.

## Avsluttende tanker

Go 1.27 er utgivelsen der mye utsatt vedlikehold forfalt samtidig.

Generiske metoder var den manglende biten i generics-arbeidet fra 1.18. Feltvelgere i struct-literaler lukket en elleve år gammel issue. `encoding/json/v2` var to års diskusjon om proposal. Og `uuid` fylte et hull som nesten alle Go-prosjekter har tettet med samme tredjepartsavhengighet i et tiår.

Hvis du vil ha en prioriteringsrekkefølge for å faktisk få verdi ut av dette:

1. **`httptest.NewTestServer` + `synctest.Sleep`**: tidsavhengige HTTP-tester blir raske og deterministiske. Beste forhold mellom innsats og gevinst i hele utgivelsen.
2. **Goroutine-lekkasjeprofilen**: eksponer ett endepunkt i staging og gjør deg klar til å bli ydmyket.
3. **Goroutine-labels i tracebacks**: tre linjer med `pprof.Do` ved inngangspunktet for forespørselen din gjør din neste stack-dump klokken 03 om til noe lesbart.
4. **`go fix ./...`**: la verktøyet gjøre moderniseringen.
5. **`encoding/json/v2`**: ingen hast. Ta det i bruk én option om gangen.

Ta `go1.27rc3` og kjør testsuiten din mot den nå. Alt på den sjekklisten er betydelig hyggeligere å oppdage en tirsdagsettermiddag enn under en hendelse.

**Referanser**

- [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)
