---
id: 1a31e06c0d969b4b6cbbc590259cf089
author: iwanhae
title: Go 1.27, czyli jak przestałem się martwić i pokochałem metody generyczne
description: Metody generyczne, encoding/json/v2, standardowy pakiet uuid oraz detektor wycieków goroutine, który faktycznie działa. Przegląd Go 1.27 z przykładami, które wszystkie zostały rzeczywiście uruchomione.
language: pl
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
---

![Przegląd Go 1.27](/assets/images/whats-new-in-go1.27/overview.webp)

Go 1.27 pojawia się w sierpniu 2026 roku i tym razem sam język się zmienił. Nie chodzi o "dodaliśmy nową funkcję do `slices`", lecz o rzeczywistą zmianę. Metody generyczne zostały wprowadzone. Jedenastoletnie zgłoszenie zostało zamknięte. `encoding/json` zostało po cichu zastąpione nowym silnikiem, gdy samolot był w powietrzu.

Każdy poniższy przykład został uruchomiony na `go1.27rc3` (darwin/arm64). Każdy blok wyjścia jest prawdziwym wynikiem, skopiowanym dosłownie, w tym komunikaty o błędach. Jeśli coś wygląda dziwnie, to dlatego, że tak właśnie odpowiedział kompilator.

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

## 1. Język się zmienił (tak, naprawdę)

### 1.1. Metody generyczne

Od Go 1.18 mamy generyki, a od Go 1.18 mamy też Rozmowę. Przebiega ona tak:

> "Napiszę po prostu metodę `Map` na moim typie slice'a..."
>
> `method must have no type parameters`
>
> "...no dobrze. Niech będzie funkcja na poziomie pakietu."

Metody mogły używać wyłącznie parametrów typów zadeklarowanych przez _receiver_. Własna metoda nie mogła wprowadzić nowych. Dlatego każda generyczna transformacja była zsyłana do zakresu pakietu, gdzie siedziała obok siedemnastu innych luźnych pomocników o nazwach będących odmianami `MapSlice`.

Go 1.27 to naprawia:

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

// F to parametr typu zadeklarowany przez samą metodę
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]
}
```

Receiver nie musi być nawet generyczny. Zwykła struktura może mieć metodę generyczną:

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

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

Zanim jednak zaczniesz dziś po południu refaktoryzować całą bazę kodu, istnieją **dwa ograniczenia**, które mają większe znaczenie, niż może się wydawać:

1. Metody interfejsów nie mogą deklarować parametrów typów.
2. Metody generyczne nie mogą implementować metod interfejsów.

To drugie jest tym, które將 Cię ugryzie:

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

Metody generyczne i dynamiczne wywołania (dispatch) więc się nie łączą. To nie skąpstwo zespołu Go: metoda generyczna ma nieskończenie wiele instancjacji, a tabela metod interfejsu musi być skończona i znana w czasie kompilacji. Nie da się umieścić czegoś nieskończonego w skończonej tabeli. Wszechświat powiedział nie.

Praktyczny wniosek: metody generyczne służą konkretnym typom o API w stylu narzędziowym. Wszystko, co ma się ukrywać za interfejsem, nadal wymaga starego podejścia.

Biblioteka standardowa już z tego skorzystała. `Rand` z `math/rand/v2` dostał metodę generyczną:

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

Wcześniej generyczna była tylko funkcja pakietowa `rand.N`, co oznaczało korzystanie z globalnego źródła. Teraz Twój własny, zasiewany `*Rand` dostaje tę samą wygodę. Drobiazg. Ale miły.

### 1.2. Selektory pól w literałach struktur, czyli: zgłoszenie #9859 wreszcie zostaje zamknięte

Osadzanie daje `u.ID`. Osadzanie _nie_ daje `User{ID: 1}`. Zamiast tego daje `User{Base: Base{ID: 1}}`, co jest sposobem Go na upewnienie się, czy naprawdę o to chodziło.

[Zgłoszenie #9859](https://go.dev/issue/9859) zostało złożone w 2015 roku. Teraz zostało zamknięte. Gdzieś gopher, który od tamtej pory dwukrotnie zmienił karierę, dostaje powiadomienie z GitHuba.

```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: użycie promowanego pola bezpośrednio
line := Line{name: "diagonal", q: Point3D{y: -4, z: 12.3}}
```

`name` nie jest polem `Line`. Jest polem osadzonego `Object`. Wcześniej trzeba było napisać `Line{Object: Object{name: "diagonal"}, ...}`.

Teraz drobny druk, który sprawdziłem, faktycznie próbując skompilować wszystko.

**(a) Kluczem nadal jest zwykły identyfikator.** Nie można zapisać dowolnej ścieżki selektora.

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

Zatem ta funkcja nie oznacza, że "klucze działają teraz jak dostęp do pól". Oznacza konkretnie, że dozwolone są domyślnie promowane nazwy pól. Jest to nieco węższe, niż sugeruje nagłówek.

**(b) Nie można jednocześnie podać osadzonego pola i czegoś promowanego z niego.**

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

Rozsądne. Podałeś dwie sprzeczne informacje o tej samej pamięci, a kompilator odmówił zgadywania.

**(c) Osadzanie wskaźników nie jest mile widziane.**

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

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

Aby podążać za wskaźnikiem, potrzebujesz wskaźnika, za którym można podążać, a w momencie konstruowania literału jeszcze go nie ma. Również to jest słuszne.

Dobra wiadomość: `go fix` przepisze za Ciebie stare literały. Więcej o tym później.

### 1.3. Wnioskowanie typów funkcji stało się mniej arbitralne

Wnioskowanie typów dla funkcji generycznych działało kiedyś w jednych miejscach, a w innych nie, bez żadnej widocznej zasady określającej, które miejsca są które. Przypisanie do zmiennej? W porządku. Umieszczenie dokładnie tej samej funkcji w polu struktury? Błąd kompilatora, proszę napisać `double[int]`, jak w 2022 roku.

Go 1.27 sprawia, że wnioskowanie działa w **każdym kontekście, w którym typ docelowy jest jednoznacznie znany**:

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

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

	var fn func(float64) float64 = double  // to zawsze działało

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

Jedna funkcja `double`, zinstancjonowana jako `func(int) int` w trzech miejscach i jako `func(float64) float64` w czwartym, w pełni na podstawie kontekstu. Jeśli piszesz struktury opcji lub tablice obsługi pełne wartości funkcyjnych, mnóstwo szumu `[T]` za chwilę zniknie z Twojej bazy kodu.

## 2. Środowisko uruchomieniowe: darmowa wydajność i detektor wycieków

### 2.1. Alokacja wyspecjalizowana rozmiarowo

Kompilator emituje teraz wywołania procedur alokacji wyspecjalizowanych rozmiarowo dla małych obiektów. Informacje o wydaniu podają do 30% mniej czasu na alokacje poniżej 80 bajtów i około 1% ogólnie dla programów intensywnie alokujących.

Nie wierzę w informacje o wydaniu bez pomiaru, więc:

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

```
# domyślnie (size-specialized malloc włączony)
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% szybciej w mikrobenchmarku, który robi wyłącznie alokacje, na Apple Silicon. Innymi słowy: to najlepszy przypadek, osiągnięty w warunkach laboratoryjnych przez benchmark zaprojektowany tak, by liczba wyglądała dobrze. W prawdziwym programie GC i rzeczywista praca przysypią większość tego zysku. Twierdzenie o ok. 1% jest tym uczciwym.

Kosztem jest rozmiar pliku binarnego. Hello World:

```
2413202 bytes  (domyślnie)
2361826 bytes  (nosizespecializedmalloc)
```

Około 50 KB, stałe, niezależnie od programu. Można z tego zrezygnować przez `GOEXPERIMENT=nosizespecializedmalloc`, ale **ta furtka ma zostać usunięta w Go 1.28**, więc traktuj ją jako obejście na potrzeby zgłoszenia błędu, a nie jako styl życia.

### 2.2. Profil wycieków goroutine jest teraz prawdziwy

Eksperymentalny w Go 1.26, ogólnie dostępny w 1.27. Zmienna GOEXPERIMENT `goroutineleakprofile` zniknęła; po prostu działa.

```go
func leak() {
	ch := make(chan int) // nikt nigdy nie wyśle. nikt.
	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
```

Trzy goroutine, trwale zablokowane, z nazwą pliku i numerem linii wskazującymi dokładnie, gdzie je utworzyłeś.

Sztuczka stojąca za tym jest naprawdę sprytna: **ponownie wykorzystuje analizę osiągalności z garbage collectora**. Jeśli goroutine G jest zablokowana na prymitywie P, a P jest nieosiągalne z żadnej uruchomionej goroutine (ani z czegokolwiek, co te goroutines mogłyby obudzić), to nic nigdy więcej nie może dotknąć P, więc G nigdy się nie obudzi. To nie jest heurystyka, to dowód.

Ten sam projekt daje za darmo również ograniczenie: jeśli kanał lub mutex jest osiągalny przez zmienną globalną lub przez zmienną lokalną jakiejś wciąż działającej goroutine, GC nadal go widzi, więc runtime nie może niczego wywnioskować. Zwróć uwagę, że nawet powyższy prosty przykład wymaga dwóch wywołań `runtime.GC()`, aby cokolwiek zgłosić. Nie wyłapie wszystkiego.

Wyłapie jednak klasyczne "zapomniałem anulować kontekst, goroutine workera żyje wiecznie", czyli większość wycieków przez większość czasu.

Jeśli importujesz `net/http/pprof`, profil jest też dostępny pod `/debug/pprof/goroutineleak`. Podłącz to na stagingu, sprawdzaj raz w tygodniu i po cichu się przerażaj.

Tę pracę wniósł Vlad Saioc z Uber, któremu należy się napój dowolnego wyboru.

### 2.3. Traceback mówi teraz, które żądanie zginęło

Dla modułów deklarujących Go 1.27 lub nowszy, nagłówki tracebacku zawierają teraz etykiety goroutine z `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}:
...
```

Ważny szczegół: **etykiety są dziedziczone przez goroutines potomne.** Goroutine 3 nigdy nie ustawiła etykiety. Dostała ją od rodzica.

Więc następnym razem, gdy na produkcji nastąpi deadlock i ktoś wyśle SIGQUIT do procesu, zamiast 4000 goroutines wyglądających identycznie, dostaniesz 4000 goroutines oznaczonych tym, do którego żądania i którego najemcy należą. Trzy linie `pprof.Do` w punkcie wejścia żądania zapewniają ci to.

Ponieważ etykiety mogą zawierać rzeczy, których nie chcesz zrzucać na stderr, `GODEBUG=tracebacklabels=0` to wyłącza, a ta opcja rezygnacji ma być wyraźnie utrzymana na zawsze.

### 2.4. asynctimerchan odeszło na dobre

Go 1.23 uczyniło kanały timerów niebuforowanymi (synchronicznymi) i udostępniło `asynctimerchan=1` jako sposób powrotu do dawnego zachowania. W 1.27 to ustawienie jest **trwale usunięte**. Kanały pakietu `time` są synchroniczne, bez dyskusji.

Interesująca jest polityka wprowadzona razem z tym. Usunięte GODEBUG pozostawione w Twoim `go.mod` nie psuje automatycznie builda; psuje go dopiero wtedy, gdy ustawiono je na **stару** wartość:

```
# go.mod zawiera: 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 zawiera: godebug asynctimerchan=0
(builds fine)
```

Oznacza to, że na reprymendę zasługują tylko ci, którzy rzeczywiście polegali na usuniętym zachowaniu. Każdy, kto ustawił wartość, która ostatecznie stała się domyślną, i o tym zapomniał, może nadal o tym nie myśleć. To przemyślany fragment archeologii API.

## 3. Biblioteka standardowa

### 3.1. encoding/json/v2: silnik wymieniono w locie

To jest duża zmiana. [Ponad dwa lata dyskusji nad propozycją](https://go.dev/issue/71497) wreszcie dobiegły końca.

Są teraz **trzy pakiety**, a zrozumienie podziału to większość sukcesu:

| Pakiet                   | Zadanie                                                                        |
| ------------------------ | ------------------------------------------------------------------------------ |
| `encoding/json`          | Znane Ci API v1. **Zachowanie w 100% niezmienione.** Teraz zaimplementowane na bazie v2 |
| `encoding/json/v2`       | Przetwarzanie semantyczne. Wartości Go ↔ JSON                                  |
| `encoding/json/jsontext` | Przetwarzanie składniowe. JSON jako strumień tokenów                           |

Najważniejsze jest to, że `encoding/json` zostało przebudowane na zupełnie innej implementacji i **zachowuje się identycznie**. Oto jak:

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

Każda starsza osobliwość v1 została zakodowana jako opcja, spakowana w `DefaultOptionsV1()`, a API v1 zawsze stosuje ten pakiet. Oznacza to, że poniższe zachowuje się tak samo w 1.26 i 1.27:

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

Tak, v1 nadal po cichu akceptuje zduplikowane klucze i bierze ostatni. Zawsze tak robiło. Nadal tak robi. Kompatybilność oznacza kompatybilność także ze złymi częściami.

**v2 natomiast ma swoje zdanie:**

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

Zduplikowane klucze i nieprawidłowy UTF-8 są odrzucane. Oba są naprawdę niebezpieczne, a nie tylko niechlujne: gdy dwa parsery nie zgadzają się co do tego, który zduplikowany klucz wygrywa, pojawiają się luki bezpieczeństwa. CVE-2017-12635 w CouchDB było dokładnie tym: treść JSON z dwoma kluczami `roles`, z których walidator odczytał jeden, a warstwa przechowywania drugi. Odrzucanie to właściwa decyzja.

Opcje są wariadyczne:

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

// posortowane klucze mapy + wcięcia
out, _ = jsonv2.Marshal(map[string]int{"b": 2, "a": 1},
	jsonv2.Deterministic(true), jsontext.WithIndent("  "))
// {
//   "a": 1,
//   "b": 2
// }

// odrzucanie nieznanych pól — wcześniej wymagało Decoder i 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` — większość rzeczy, które wcześniej rozwiązywano bibliotekami zewnętrznymi lub ręcznie pisanym `MarshalJSON`, jest teraz flagami.

A historia migracji jest najlepszą częścią. **Późniejsze opcje mają pierwszeństwo**, więc możesz przyjmować surowość v2 po jednym zachowaniu naraz:

```go
// zachowaj semantykę v1, ale odrzucaj zduplikowane klucze jak v2
jsonv2.Unmarshal(data, &v,
	json.DefaultOptionsV1(),
	jsontext.AllowDuplicateNames(false))
// duplicate object member name
```

Nie musisz przenosić bazy kodu liczącej 200 tysięcy linii na v2, aby przestać akceptować zduplikowane klucze. Przełączasz jedną opcję. Przy dużych projektach to realistyczna droga.

Wydajność: marshalling jest mniej więcej na poziomie v1, **unmarshalling jest znacznie szybszy.** Gdyby coś poszło źle, `GOEXPERIMENT=nojsonv2` przywraca starą implementację, a ta furtka również w końcu zostanie usunięta, więc zgłoś problem zamiast się w niej urządzać.

`jsontext` to warstwa niskopoziomowa: `Encoder`/`Decoder` przechodzące przez JSON jako `Token`y i `Value`e z maszyną stanów, która pilnuje porządku. Sięgaj po nią, gdy piszesz strumieniowy transformator lub filtr JSON i nie chcesz wcale materializować wartości Go.

### 3.2. Standardowy pakiet uuid

Wreszcie. RFC 9562 w bibliotece standardowej, bez potrzeby `go get`.

```go
import "uuid"

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

	// v7: górne 48 bitów to znacznik czasu, więc te zawsze sortują się w kolejności utworzenia
	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
}
```

Całe API to `New`, `NewV4`, `NewV7`, `Nil`, `Max`, `Parse`, `MustParse` oraz jeden typ: `type UUID [16]byte`. To wszystko. Możesz przeczytać całą dokumentację pakietu, zanim ostygnie kawa.

Szczegóły warte uwagi:

- **`UUID` to `[16]byte`**, więc `==` działa i można go bezpośrednio użyć jako klucza mapy. Ten sam projekt co `google/uuid`.
- **`Nil` i `Max` są funkcjami, a nie zmiennymi.** Ponieważ pakietowe `var Nil UUID` to nabity pistolet wycelowany w Twoją stopę, a ktoś, gdzieś, w końcu by do niej przypisał.
- Losowe bity pochodzą z **kryptograficznie bezpiecznego** generatora.
- Implementuje `encoding.TextMarshaler`/`TextUnmarshaler`/`TextAppender`, więc pasuje bezpośrednio do struktur JSON.
- **`NewV7` to ten, którego chcesz dla kluczy głównych w bazie danych.** Uporządkowanie czasowe oznacza, że indeks B-drzewa przestaje się fragmentować, w czym UUID v4 są notorycznie złe.

Możesz teraz usunąć zależność. Jeśli potrzebujesz v1/v3/v5 lub bardziej zaawansowanych opcji parsowania, biblioteki zewnętrzne nadal mają swoje zadanie.

### 3.3. crypto/mldsa: podpisy postkwantowe i są ogromne

Go 1.24 dało nam `crypto/mlkem` do postkwantowej wymiany kluczy. Go 1.27 wprowadza drugą połowę: podpisy ML-DSA, standaryzowane jako 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
```

Spójrz na te liczby. **Pojedynczy podpis ma 3309 bajtów.** Ed25519 ma 64. To wzrost około 50 razy, a klucz publiczny ma prawie 2 KB ponad to. Wrzuć kilka takich do łańcucha certyfikatów, a Twój handshake TLS zacznie potrzebować własnej strategii MTU.

To jest rzeczywisty koszt odporności kwantowej dzisiaj i dlatego nikt nie przełącza wszystkiego już jutro. Ale jest to teraz w bibliotece standardowej, czyli tam, gdzie chcesz, żeby było, _zanim_ będziesz tego potrzebować.

`Options.Context` to separacja domen: podpisuj tym samym kluczem do różnych celów, używaj innego kontekstu dla każdego, a podpis z jednego kontekstu nie zweryfikuje się w innym. Powyższy przykład pokazuje dokładnie to: ten sam klucz, ta sama wiadomość, inny kontekst, odrzucone.

`PrivateKey` implementuje `crypto.Signer`, więc pasuje do istniejących interfejsów. `crypto/x509` obsługuje klucze i podpisy ML-DSA, a `crypto/tls` obsługuje schematy podpisów `MLDSA44`/`MLDSA65`/`MLDSA87` w TLS 1.3.

Jest też `SignDeterministic`, który pomija losowość, co przydaje się w testach i powtarzalnych buildach.

### 3.4. simd: instrukcje wektorowe bez assemblera

Go 1.26 wprowadziło eksperymentalnie specyficzny dla architektury `simd/archsimd`. Go 1.27 dodaje `simd`, który jest **przenośny i niezależny od szerokości wektora**. Włącza się przez `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:])
	}
	// obsługa ogona częściowym ładowaniem/zapisem — bez skalarnej pętli porządkowej
	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]
```

Wybór projektowy, który ma znaczenie: **szerokość wektora nigdy nie jest zakodowana na sztywno.** Pytasz `va.Len()` w czasie wykonania. `VectorBitSize()` mówi, jaka jest rzeczywista szerokość, a `Emulated()` mówi, czy masz prawdziwy sprzęt, czy uprzejmą programową imitację. Mój Mac zgłosił 128-bitowy NEON; maszyna z AVX-512 zgłasza więcej; maszyna bez niczego zgłasza emulację, a kod nadal działa.

`LoadFloat32sPart`/`StorePart` zasługują na szczególną wzmiankę. Najbardziej uciążliwą częścią ręcznie pisanego SIMD jest zawsze nierówny ogon na końcu tablicy, a to obsługuje bez osobnej pętli skalarnej.

Nadal eksperymentalne, API nadal niestabilne, nie wdrażaj tego na produkcji. Ale fakt, że to kod Go, a nie assembler ani cgo, jest naprawdę wielką sprawą.

### 3.5. hash/maphash.Hasher

Nowy interfejs opisujący kontrakt między typem wartości a kontenerami opartymi na haszowaniu:

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

Dlaczego? Wbudowana mapa Go akceptuje tylko klucze `comparable`. Nie możesz użyć slice'a jako klucza. Nie możesz zdefiniować "równości z ignorowaniem wielkości liter". `Hasher` rozwiązuje oba problemy naraz:

```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)   // ten sam seed, bo inaczej cała ta zabawa nie ma sensu
	hr.Hash(&h, v)
	return h.Sum64()
}

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

Dla zwykłej semantyki `==` istnieje `ComparableHasher[T]`:

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

Haczyk: **`Hasher` to interfejs, a nie struktura danych.** Nie ma jeszcze standardowej tablicy haszującej ani filtru Blooma, która by z niego korzystała. To fundament pod przyszły pakiet kontenerów. Dziś użyjesz go, budując własną strukturę, albo korzystając z istniejącej implementacji, jak `go/types.Hasher` (który pozwala użyć `types.Type` jako klucza mapy, respektując `Identical`).

Zarządzanie seedem spoczywa na Tobie. Gdyby `hashOf` powyżej tworzyło nowy seed przy każdym wywołaniu, identyczne wartości dawałyby różne skróty i przykład wypisałby `false`, czyli dokładnie błąd, który popełniłem przy pierwszej próbie. Jeden seed na kontener. (Seed jest losowy, aby uniemożliwić ataki DoS typu hash-flooding, dlatego nie jest po prostu stałą.)

### 3.6. httptest.NewTestServer + synctest.Sleep: trafienie w usypiacza

Jeśli z tego wydania przyjmiesz tylko jedną rzecz, niech to będzie ta.

`testing/synctest` weszło do użytku w Go 1.25 i dało nam sztuczny zegar do testowania współbieżnego kodu. Problem polegał na tym, że w chwili, gdy test dotknął prawdziwej sieci, iluzja się załamywała. `httptest.NewTestServer` w Go 1.27 używa **wirtualnej sieci w pamięci**, więc bańka pozostaje nienaruszona. A `synctest.Sleep` (= `time.Sleep` + `synctest.Wait`) dopełnia całość.

Oto test ponawiania z wykładniczym backoffem:

```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 powinien wynosić dokładnie 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)
```

Przeczytaj to dwa razy. Zasymulowało trzy sekundy backoffu wobec prawdziwego serwera HTTP i zakończyło się w **0.00 sekundy**. A asercja to `elapsed == 3*time.Second`, _dokładnie_ trzy sekundy, a nie "co najmniej trzy sekundy, plus minus jitter harmonogramu". Sztuczne zegary nie mają jittera.

`synctest.Sleep` istnieje z konkretnego powodu: jeśli test śpi przez ten sam czas, co testowany kod, to który się obudzi pierwszy, jest czyjąś zgadywanką. `synctest.Sleep` śpi _a potem_ czeka, aż każda inna goroutine w bańce będzie trwale zablokowana, więc obserwujesz system po tym, jak się ustabilizował.

Timeouty, ponowienia, wyłączniki obwodu, ograniczniki szybkości: każdy zależny od czasu test klienta HTTP, który posiadasz, może stać się szybki i deterministyczny. Jeśli Twój zestaw testów jest obecnie trzymany razem przez `time.Sleep(100 * time.Millisecond)` i nadzieję, to jest Twoje wyjście.

### 3.7. Zmiany w net/http, które naprawdę wpływają na produkcję

Ciche, ale pojawią się w Twoich metrykach.

**Ciała odpowiedzi HTTP/1 są automatycznie opróżniane przy Close.** Nieprzeczytana treść jest teraz opróżniana (do zachowawczego limitu) przy zamykaniu ciała, dzięki czemu połączenie może zostać ponownie użyte. Oznacza to, że w końcu możesz usunąć tę formułkę, którą wszyscy kopiują z tej samej odpowiedzi na Stack Overflow od 2016 roku:

```go
// nie jest już potrzebne
defer func() {
	io.Copy(io.Discard, resp.Body)
	resp.Body.Close()
}()
```

Dla większości programów to brak efektu lub niewielki zysk. Jeśli sytuacja się _pogorszy_, prawdopodobnie znajdujesz się w kategorii, którą informacje o wydaniu grzecznie opisują: `Transport.MaxIdleConns` ustawione na 0 albo nowy `Client` na każde żądanie, co całkowicie omija limit bezczynnych połączeń. `Transport.DisableKeepAlives = true` zamaskuje to, ale rzeczywista rada z informacji o wydaniu brzmi, że "głębsza analiza prawdopodobnie byłaby korzystna", co w języku zespołu Go oznacza: _masz większe problemy_.

**Priorytet klienta HTTP/2 (RFC 9218).** Serwer honoruje teraz sygnały priorytetu klienta. Jeśli wolisz dawne szeregowanie round-robin, ustaw `Server.DisableClientPriority = true`.

**`Server.MaxHeaderValueCount`.** Ogranicza liczbę wartości nagłówków, jaką serwer zaakceptuje, domyślnie `DefaultMaxHeaderValueCount`. Kolejne drzwi zamknięte dla "wyślij dziesięć tysięcy nagłówków i zobacz, co się stanie".

**ALPN na połączeniach dostarczonych przez użytkownika.** Jeśli Twój `net.Conn` implementuje `ConnectionState() tls.ConnectionState`, `Transport` i `Server` wykonają na nim negocjację TLS ALPN, więc niestandardowe dialery przechodzące przez proxy nadal mogą negocjować HTTP/2.

### 3.8. Drobnostki, które wywołują radość

**`strings.CutLast` / `bytes.CutLast`.** `Cut` dzieli na _pierwszym_ separatorze. Nie było wariantu "ostatni", więc wszyscy ręcznie pisali `LastIndex` i wycinanie, a około 30% z nas za pierwszym razem popełniło błąd o jeden.

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

Świetne do rozszerzeń plików i do parsowania `host:port`, gdzie trzeba znaleźć _ostatni_ dwukropek, bo adresy IPv6 są ich pełne.

**`math/big.Int.Divide`.** Dzielenie z jawnym trybem zaokrąglania, zastępujące rzut monetą "czy chcę `Quo` czy `Div`?":

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

Jeśli pracujesz w dziedzinie, w której reguła zaokrąglania jest zapisana w przepisach, to jest dla Ciebie.

**`url.URL.Clone` i `url.Values.Clone`.** Kopie głębokie. Stara płytka kopia `*u` współdzieliła wskaźnik `Userinfo`, co dawało dokładnie taki błąd, którego znalezienie zajmuje dzień, a naprawa pięć sekund.

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

**Metody odczytu `net.UnixConn`** zwracają teraz `io.EOF` bezpośrednio, zamiast opakowywać go w `net.OpError`. `err == io.EOF` działa teraz, tak jak zawsze powinno.

**`database/sql.ConvertAssign` i `driver.RowsColumnScanner`.** Funkcje dla autorów sterowników. Pierwsza udostępnia konwersje typów, które wykonuje `Rows.Scan`; druga pozwala sterownikom skanować bezpośrednio do docelowych miejsc użytkownika, pomijając pośrednią alokację.

**`unicode` 15 → 17.** Dwie wersje jednym skokiem. Klasyfikacja napisów i zachowanie normalizacji mogą się subtelnie zmienić. Jeśli masz testy, które od tego zależą, dowiesz się o tym.

**`compress/flate` działa szybciej** — a **jego bajty wyjściowe mogą różnić się od Go 1.26.** To przenosi się na `archive/zip`, `compress/gzip`, `compress/zlib` i `image/png`. Jeśli masz testy golden-file, które haszują skompresowane dane wyjściowe, to się posypią, i nie będzie to regresja. Sprawdź to _przed_ aktualizacją, a nie podczas przeglądu incydentu.

## 4. Toolchain

### 4.1. go fix zyskało więcej modernizatorów

`go fix` po cichu staje się narzędziem do modernizacji kodu. Go 1.27 dodaje `atomictypes`, `embedlit`, `slicesbackward` i `unsafefuncs`. Użyj `-diff`, aby zobaczyć podgląd:

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

Cztery modernizatory zadziałały na jednym małym pliku:

- `embedlit` — przepisuje osadzone literały na nową składnię z §1.2
- `atomictypes` — `atomic.AddInt64(&x, 1)` staje się `atomic.Int64.Add(1)`, co uniemożliwia niatomowy dostęp i po cichu naprawia błędy wyrównania w 32-bitowych systemach, o których nie wiedziałeś
- `slicesbackward` — pętle wsteczne stają się `slices.Backward`
- `waitgroupgo` — rytuał `Add`/`go`/`Done` staje się `wg.Go` (zmieniono nazwę z `waitgroup` z 1.26, aby uniknąć niejednoznaczności)

`go tool fix help` wymienia wszystkie 26; `go tool fix help <name>` wyjaśnia jeden z nich. Ponadto `fmtappendf` usunięto "ze względu na kwestie stylistyczne", co jest pięknie dyplomatycznym opisem tego, co wydarzyło się w wątku tego zgłoszenia.

Uruchomienie `go fix ./...` na starej bazie kodu to naprawdę satysfakcjonujące popołudnie. Oczywiście najpierw przeczytaj diff.

### 4.2. go test domyślnie uruchamia stdversion

To jest zmiana, która najprawdopodobniej przerwie Twój dzień.

`go test` uruchamia teraz domyślnie sprawdzenie vet `stdversion`, oznaczające symbole biblioteki standardowej nowsze niż dyrektywa `go` w Twoim `go.mod`:

```go
// go.mod mówi: go 1.24
package sv

import "strings"

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

To eliminuje klasyczny tryb awarii, w którym wszystko działa na Twojej maszynie (najnowszy toolchain), a eksploduje w CI lub w starszym środowisku użytkownika. Jeśli publikujesz biblioteki z zachowawczą dyrektywą `go`, to prezent.

### 4.3. go test -json zyskało OutputType

Linie `"Action":"output"` zawierają teraz opcjonalne pole `"OutputType"`: `"error"`, `"error-continue"` lub `"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'
```

Dane wyjściowe `t.Log` (bez pola), dane wyjściowe `t.Error` (`error`) i linie generowane przez framework (`frame`) są teraz rozróżnialne. Każdy, kto napisał wyrażenie regularne, by ustalić, które linie wyjścia testu są "prawdziwe", może je teraz usunąć.

### 4.4. Ulepszenia go doc

**Składnia `package@version`.** Czytaj dokumentację konkretnej wersji bez dodawania jej do swojego modułu:

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

Przydatne do sprawdzenia, co się zmieniło przed aktualizacją.

**`-ex` i wypisywanie kodu przykładów:**

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

Kod przykładu _i_ oczekiwane wyjście, w terminalu, bez otwierania karty przeglądarki, która nadal będzie otwarta w przyszły czwartek.

### 4.5. go mod tidy porządkuje Twoje bloki require

Dla modułów z `go 1.27` lub nowszym, `go mod tidy` scala rozproszone bloki `require` w co najwyżej dwa (bezpośrednie i pośrednie), zachowując dołączone komentarze.

```go
// Przed
module tidydemo

go 1.27

require golang.org/x/sync v0.10.0

// networking
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
// Po: go mod tidy
module tidydemo

go 1.27

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

Zwróć uwagę, że komentarz `// networking` przetrwał. Ta zmiana głównie sprząta po **rozwiązywaniu konfliktów scalania w Git**, gdzie rodzą się błąkające się bloki `require`. Jeśli w Twoim zespole więcej niż trzy osoby dodają zależności, konflikty w Twoim `go.mod` staną się zauważalnie cichsze.

### 4.6. Różne drobiazgi

- **Usunięto obsługę `bzr`.** Jeśli to Cię dotyczy, naprawdę chciałbym usłyszeć tę historię.
- **`go tool trace -http` wiąże się z localhost**, gdy podano tylko port. `-http=:6060` już nie nasłuchuje na każdym interfejsie. Użyj `-http=0.0.0.0:6060`, jeśli tego właśnie chcesz. To ujednolica zachowanie z `go tool pprof` i zapobiega sporadycznemu, przypadkowemu publicznemu udostępnieniu profilera.
- **Pliki odpowiedzi (`@file`)** są obsługiwane przez `compile`, `link`, `asm`, `cgo`, `cover` i `pack`, w formacie zgodnym z GCC. Dla systemów budowania, które przekraczają limity długości wiersza poleceń — witaj, Bazel.

## 5. Kompilator, linker, porty

**Kompilator.** Względne nazwy plików w dyrektywach `//line` są teraz rozwiązywane względem katalogu pliku zawierającego, zgodnie z `go/scanner`. Istotne, jeśli piszesz generatory kodu.

Nazwy symboli literałów funkcyjnych (domknięć) są też teraz prostsze — ta sama nazwa niezależnie od inlinowania, a wiele instancji tego samego literału może dzielić kod w pliku binarnym. Bez zmiany funkcjonalnej, _z wyjątkiem_: kod porównujący tożsamość funkcji przez `reflect.Value.Pointer` będzie częściej widział "równe" niż wcześniej. To porównanie nigdy nie było poprawne, ale jeśli je masz, teraz zacznie kłamać głośniej.

**Linker.** Nowe opcje `-macos` i `-macsdk` ustawiają wersje systemu i SDK w poleceniu ładującym `LC_BUILD_VERSION` na macOS.

**Porty.**

- **Darwin wymaga teraz macOS 13 Ventura lub nowszego**, jak zapowiedziano w 1.26. Sprawdź swoje runnery CI.
- **PowerPC (`GOOS=linux GOARCH=ppc64`) przeszło na ABI ELFv2.** Wymaga jądra Linux 3.13+ (RHEL7 z backportem do 3.10). Cgo, PIE i linkowanie zewnętrzne są teraz obsługiwane. Jeśli używasz cgo, ale potrzebujesz statycznego, czysto Go pliku binarnego, ustaw `CGO_ENABLED=0`.

## 6. Lista kontrolna aktualizacji

Go 1.27 poważnie traktuje kompatybilność, ale przed podniesieniem wersji sprawdź te punkty:

- [ ] **Testy golden-file na skompresowanych danych wyjściowych.** `compress/flate` zmienił kodery; bajty gzip/zip/png mogą się różnić.
- [ ] **Testy dopasowujące komunikaty błędów JSON.** Zachowanie jest identyczne; **tekst błędu nie jest.**
- [ ] **Usunięte GODEBUG w `go.mod`.** `asynctimerchan`, `gotypesalias`, `tlsrsakex`, `tls3des`, `tls10server`, `tlsunsafeekm`, `x509keypairleaf` — te psują build tylko wtedy, gdy ustawione są na swoje _stare_ wartości.
- [ ] **Naruszenia `stdversion`.** `go test` wykrywa je teraz. Uruchom wcześnie, jeśli utrzymujesz bibliotekę z zachowawczą dyrektywą `go`.
- [ ] **Runnery CI z macOS 12 lub starszym.** Wsparcie zniknęło.
- [ ] **Testy sprawdzające nazwy symboli literałów funkcyjnych** oraz **porównania funkcji przez `reflect.Value.Pointer`.**
- [ ] **`Transport.MaxIdleConns = 0`** albo `Client` na każde żądanie. W połączeniu z automatycznym opróżnianiem ciał odpowiedzi może to spowolnić działanie.

## Końcowe przemyślenia

Go 1.27 to wydanie, w którym naraz upłynął termin wielu odłożonych prac konserwacyjnych.

Metody generyczne były brakującym elementem prac nad generykami z 1.18. Selektory pól w literałach struktur zamknęły jedenastoletnie zgłoszenie. `encoding/json/v2` to dwa lata dyskusji nad propozycją. A `uuid` wypełnił lukę, którą praktycznie każdy projekt Go łatał tą samą zewnętrzną zależnością przez dekadę.

Jeśli chcesz kolejności priorytetów, by faktycznie wyciągnąć wartość z tego wydania:

1. **`httptest.NewTestServer` + `synctest.Sleep`** — testy HTTP zależne od czasu stają się szybkie i deterministyczne. Najlepszy stosunek wysiłku do korzyści w całym wydaniu.
2. **Profil wycieków goroutine** — udostępnij jeden endpoint na stagingu i przygotuj się na pokorę.
3. **Etykiety goroutine w tracebackach** — trzy linie `pprof.Do` w punkcie wejścia żądania zamieniają Twój następny zrzut stosu o 3 w nocy w coś czytelnego.
4. **`go fix ./...`** — pozwól narzędziu zrobić modernizację.
5. **`encoding/json/v2`** — bez pośpiechu. Przyjmuj to po jednej opcji naraz.

Weź `go1.27rc3` i uruchom teraz swój zestaw testów. Wszystko z tej listy kontrolnej znacznie przyjemniej odkryć we wtorkowe popołudnie niż podczas incydentu.

**Referencje**

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