---
id: 1a31e06c0d969b4b6cbbc590259cf089
author: iwanhae
title: Go 1.27, aneb Jak jsem se naučil přestat se strachovat a zamilovat si generické metody
description: Generické metody, encoding/json/v2, standardní balíček uuid a detektor úniku goroutin, který skutečně funguje. Přehled jazyka Go 1.27 s příklady, které byly všechny skutečně spuštěny.
language: cs
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
---

![Přehled Go 1.27](/assets/images/whats-new-in-go1.27/overview.webp)

Go 1.27 vyjde v srpnu 2026 a tentokrát se změnil samotný jazyk. Nejde o to, že jsme do `slices` přidali novou funkci, ale o skutečnou změnu. Generické metody jsou zavedeny. Jeden jedenáctiletý issue byl uzavřen. `encoding/json` byl tiše nahrazen novým enginem, zatímco letadlo bylo ve vzduchu.

Každý příklad níže byl spuštěn na `go1.27rc3` (darwin/arm64). Každý výstupní blok je skutečný výstup, zkopírovaný včetně chybových hlášení. Pokud se něco zdá podivné, je to proto, že to kompilátor skutečně vypsal.

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

## 1. Jazyk se změnil (ano, opravdu)

### 1.1. Generické metody

Od Go 1.18 máme generika a od Go 1.18 máme také Rozhovor. Vypadá takto:

> „Jen napíšu metodu `Map` na svůj slice typ…“
>
> `method must have no type parameters`
>
> „…dobře. Tak to bude funkce na úrovni balíčku.“

Metody mohly používat pouze typové parametry deklarované v _receiveru_. Vlastní metoda nemohla zavádět nové. Každá generická transformace tedy skončila v package scope, kde ležela vedle dalších sedmnácti volně plovoucích pomocných funkcí s nějakou variantou názvu `MapSlice`.

Go 1.27 to opravuje:

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

// F je typový parametr deklarovaný samotnou metodou
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 ani nemusí být generický. Obyčejná struktura může mít generickou metodu:

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

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

Než ale dnes odpoledne začnete refaktorovat celou kódovou základnu, existují **dvě omezení** a jsou důležitější, než se na první pohled zdá:

1. Metody rozhraní nemohou deklarovat typové parametry.
2. Generické metody nemohou implementovat metody rozhraní.

Právě to druhé vás nejspíš potrápí:

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

Generické metody a dynamická dispatch se tedy nekombinují. Nejde o skoupost týmu Go: generická metoda má nekonečně mnoho instancí, zatímco tabulka metod rozhraní musí být konečná a známá v době kompilace. Nekonečnou věc nelze uložit do konečné tabulky. Vesmír řekl ne.

Praktické shrnutí: generické metody jsou určeny pro konkrétní typy s API ve tvaru užitných funkcí. Cokoli, co se musí skrývat za rozhraním, stále potřebuje starý přístup.

Standardní knihovna toho již využila. `Rand` z `math/rand/v2` dostal generickou metodu:

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

Dříve byla generická pouze funkce na úrovni balíčku `rand.N`, která používala globální zdroj. Nyní má vlastní nastavený `*Rand` stejné pohodlí. Malá věc. Hezká věc.

### 1.2. Selektory polí ve struct literálech, neboli: issue #9859 konečně doputoval do cíle

Embedding vám dá `u.ID`. Embedding vám ale nedá `User{ID: 1}`. Místo toho dostanete `User{Base: Base{ID: 1}}`, což je způsob, jakým se Go ptá, zda jste to opravdu mysleli.

[Issue #9859](https://go.dev/issue/9859) bylo založeno v roce 2015. Nyní je uzavřeno. Někde gopher, který mezitím dvakrát změnil kariéru, právě dostává notifikaci z GitHubu.

```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: použijte povýšené pole přímo
line := Line{name: "diagonal", q: Point3D{y: -4, z: 12.3}}
```

`name` není polem `Line`. Je polem vloženého `Object`. Dříve jste museli psát `Line{Object: Object{name: "diagonal"}, ...}`.

Nyní drobné podrobnosti, které jsem ověřil tím, že jsem se skutečně pokusil zkompilovat všechno.

**(a) Klíčem je stále prostý identifikátor.** Nelze zapsat libovolnou cestu selektoru.

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

Funkce tedy neznamená, že „klíče nyní fungují jako přístup k polím“. Znamená konkrétně, že jsou povoleny implicitně povýšené názvy polí. Je to o něco užší, než naznačuje titulek.

**(b) Nelze současně určit vložené pole a něco, co je z něj povýšeno.**

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

Rozumné. Řekli jste kompilátoru dvě protichůdné věci o stejné paměti a on odmítl hádat.

**(c) Vkládání ukazatelů není vítáno.**

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

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

Abyste mohli sledovat ukazatel, potřebujete ukazatel, který můžete sledovat, a v okamžiku konstrukce literálu ještě žádný neexistuje. I to je férové.

Dobrá zpráva: `go fix` vaše staré literály přepíše za vás. K tomu se dostaneme později.

### 1.3. Odvozování typů funkcí je méně libovolné

Odvozování typů pro generické funkce fungovalo dříve na některých místech a na jiných ne, bez jakéhokoli rozpoznatelného principu, podle něhož se to rozhodovalo. Přiřazení do proměnné? V pořádku. Vložení stejné funkce do pole struktury? Chyba kompilátoru, prosím napište `double[int]`, jako bychom psali rok 2022.

Go 1.27 zajišťuje, že odvozování funguje **v každém kontextu, kde je cílový typ jednoznačně známý**:

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

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

	var fn func(float64) float64 = double  // toto fungovalo vždy

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

Jedna funkce `double` se instancuje jako `func(int) int` na třech místech a jako `func(float64) float64` na čtvrtém, a to čistě z kontextu. Pokud píšete option struktury nebo handler tabulky plné funkčních hodnot, spousta šumu v podobě `[T]` z vaší kódové základny brzy zmizí.

## 2. Runtime: zdarma výkon a detektor úniků

### 2.1. Alokace specializovaná podle velikosti

Kompilátor nyní pro malé objekty generuje volání alokačních rutin specializovaných podle velikosti. Poznámky k vydání slibují až 30% úsporu u alokací menších než 80 bajtů a zhruba 1% celkově u programů náročných na alokace.

Poznámkám k vydání nevěřím bez měření, takže:

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

```
# výchozí (alokace specializovaná podle velikosti zapnuta)
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

```

Na Apple Silicon je to o 30–35 % rychlejší v mikrobenchmarku, který nedělá nic jiného než alokuje. Jinými slovy: jde o nejlepší možný případ, dosažený v laboratorních podmínkách benchmarkem navrženým tak, aby číslo vypadalo dobře. V reálném programu většinu z toho zahltí GC a skutečná práce. Tvrzení o ~1 % je to poctivé.

Cenou je velikost binárního souboru. Hello World:

```
2413202 bytes  (výchozí)
2361826 bytes  (nosizespecializedmalloc)
```

Přibližně 50 KB navíc, pevně, bez ohledu na váš program. Můžete se odhlásit pomocí `GOEXPERIMENT=nosizespecializedmalloc`, ale **tento únikový východ je naplánován k odstranění v Go 1.28**, takže ho berte jako obejití pro hlášení chyb, a ne jako životní styl.

### 2.2. Profil úniku goroutin je nyní skutečný

V Go 1.26 experimentální, v 1.27 obecně dostupný. GOEXPERIMENT `goroutineleakprofile` zmizel; prostě funguje.

```go
func leak() {
	ch := make(chan int) // nikdo nikdy nepošle. nikdo.
	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
```

Tři goroutiny natrvalo uvízlé, se souborem a číslem řádku, které přesně ukazují, kde jste to udělali.

Trik za tím je opravdu chytrý: **znovu využívá analýzu dosažitelnosti garbage collectoru**. Pokud je goroutina G blokována na primitivu P a P není dosažitelný z žádné spustitelné goroutiny (ani z ničeho, co by tyto goroutiny mohly probudit), pak se k P už nikdo nemůže dostat, a G se tedy nikdy neprobudí. Nejde o heuristiku, ale o důkaz.

Stejný návrh vám zároveň přináší jeho omezení zadarmo: pokud je kanál nebo mutex dosažitelný přes globální proměnnou nebo přes lokální proměnnou nějaké stále běžící goroutiny, GC je stále vidí, a runtime tedy nemůže nic vyvodit. Všimněte si, že i tento hračkový příklad potřebuje dvě volání `runtime.GC()`, aby se něco nahlásilo. Nezachytí všechno.

Zachytí však klasické „zapomněl jsem zrušit context, worker goroutina žije věčně“, což je většina úniků ve většině případů.

Pokud importujete `net/http/pprof`, je profil dostupný také na `/debug/pprof/goroutineleak`. Zapojte to do stagingu, kontrolujte to každý týden a tiše se děste.

Tuto práci přispěl Vlad Saioc z Uberu, který si zaslouží nápoj dle vlastní volby.

### 2.3. Tracebacky nyní říkají, který požadavek selhal

U modulů deklarujících Go 1.27 nebo novější obsahují řádky hlaviček tracebacku nyní štítky goroutin 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}:
...
```

Důležitý detail: **štítky dědí podřízené goroutiny.** Goroutina 3 nikdy žádný štítek nenastavila. Dostala ho od svého rodiče.

Až tedy příště v produkci dojde k deadlocku a někdo pošle procesu SIGQUIT, místo 4 000 goroutin, které vypadají úplně stejně, dostanete 4 000 goroutin označených tím, ke kterému požadavku a kterému tenantovi patří. Tři řádky `pprof.Do` na vstupním bodu vašich požadavků vám to zajistí.

Protože štítky mohou obsahovat věci, které nechcete vypisovat do stderr, `GODEBUG=tracebacklabels=0` tuto funkci vypne a tato možnost odhlášení je výslovně zamýšlena jako trvale dostupná.

### 2.4. asynctimerchan je navždy pryč

Go 1.23 nastavilo kanály časovačů jako nebufferované (synchronní) a nabídlo `asynctimerchan=1` jako cestu zpět ke starému chování. V 1.27 je toto nastavení **trvale odstraněno**. Kanály balíčku `time` jsou synchronní, bez výjimky a bez vyjednávání.

Zajímavá je politika, která byla zavedena spolu s tím. Odstraněný GODEBUG ponechaný ve vašem `go.mod` automaticky nerozbije build. Rozbije ho pouze tehdy, když je nastaven na **starou** hodnotu:

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

To znamená, že na vás budou křičet pouze lidé, kteří se na odstraněné chování skutečně spoléhali. Všichni, kdo nastavili výslednou výchozí hodnotu a zapomněli na to, si mohou dál nemyslet na to. Je to promyšlený kus archeologie API.

## 3. Standardní knihovna

### 3.1. encoding/json/v2: motor byl vyměněn za jízdy

Toto je velká věc. [Více než dva roky diskuse o návrhu](https://go.dev/issue/71497) konečně dospěly k výsledku.

Nyní existují **tři balíčky** a pochopení jejich rozdělení je většinou úspěchu:

| Balíček                  | Úloha                                                                          |
| ------------------------ | ------------------------------------------------------------------------------ |
| `encoding/json`          | API v1, které znáte. **Chování zůstalo na 100 % nezměněno.** Nyní je implementováno nad v2 |
| `encoding/json/v2`       | Sémantické zpracování. Hodnoty Go ↔ JSON                                        |
| `encoding/json/jsontext` | Syntaktické zpracování. JSON jako proud tokenů                                  |

Hlavní zpráva je, že `encoding/json` byl přestavěn na zcela jiné implementaci a **chová se identicky**. Jak to funguje:

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

Každá legacy zvláštnost v1 byla zakódována jako option, sbalena do `DefaultOptionsV1()` a API v1 tento balík vždy použije. Díky tomu se toto chová stejně v 1.26 i 1.27:

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

Ano, v1 stále tiše přijímá duplicitní klíče a použije poslední z nich. Vždy to takto dělal. Dělá to i nadále. Kompatibilita znamená kompatibilitu i se špatnými částmi.

**v2 mezitím má své názory:**

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

Duplicitní klíče a neplatné UTF-8 jsou odmítnuty. Obojí je skutečně nebezpečné, nejen neuspořádané: když se dva parsery neshodnou na tom, který duplicitní klíč vyhrává, vznikají bezpečnostní chyby. Právě takový byl CVE-2017-12635 v CouchDB: JSON tělo se dvěma klíči `roles`, přičemž validátor přečetl jeden a úložná vrstva druhý. Odmítnutí je správné rozhodnutí.

Options jsou variadické:

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

// seřazené klíče mapy + odsazení
out, _ = jsonv2.Marshal(map[string]int{"b": 2, "a": 1},
	jsonv2.Deterministic(true), jsontext.WithIndent("  "))
// {
//   "a": 1,
//   "b": 2
// }

// odmítnout neznámá pole — dříve vyžadovalo Decoder a 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`: většina věcí, které jste dříve řešili externí knihovnou nebo ručně psaným `MarshalJSON`, je nyní přepínačů.

Nejlepší část je ale příběh migrace. **Pozdější options mají přednost**, takže můžete přijímat přísnost v2 po jednom chování:

```go
// zachovat sémantiku v1, ale odmítat duplicitní klíče jako v2
jsonv2.Unmarshal(data, &v,
	json.DefaultOptionsV1(),
	jsontext.AllowDuplicateNames(false))
// duplicate object member name
```

Nemusíte portovat kódovou základnu o 200 tisících řádcích na v2, abyste přestali přijímat duplicitní klíče. Přepnete jednu option. U čehokoli velkého je to realistická cesta.

Výkon: marshalling je zhruba na úrovni původního, **unmarshalling je výrazně rychlejší.** Pokud se něco pokazí, `GOEXPERIMENT=nojsonv2` obnoví starou implementaci. I tato možnost odhlášení bude časem odstraněna, takže raději založte issue, než abyste se v ní usadili.

`jsontext` je nízkoúrovňová vrstva: `Encoder`/`Decoder`, které procházejí JSON jako `Token` a `Value` se stavovým automatem, který vás udržuje v poctivosti. Sáhněte po něm, když píšete streamovací transformátor nebo JSON filtr a nechcete vůbec materializovat hodnoty Go.

### 3.2. Standardní balíček uuid

Konečně. RFC 9562 ve standardní knihovně, bez nutnosti `go get`.

```go
import "uuid"

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

	// v7: horních 48 bitů je časová značka, takže tyto hodnoty se vždy řadí podle času vzniku
	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
}
```

Celé API tvoří `New`, `NewV4`, `NewV7`, `Nil`, `Max`, `Parse`, `MustParse` a jeden typ: `type UUID [16]byte`. To je vše. Celou dokumentaci balíčku si můžete přečíst, zatímco vám vychladne káva.

Podrobnosti, které stojí za zmínku:

- **`UUID` je `[16]byte`**, takže `==` funguje a lze jej přímo použít jako klíč mapy. Stejný návrh jako `google/uuid`.
- **`Nil` a `Max` jsou funkce, nikoli proměnné.** Protože package-level `var Nil UUID` je nabitá zbraň namířená na vaši nohu a někdo by do ní nakonec něco přiřadil.
- Náhodné bity pocházejí z **kryptograficky bezpečného** generátoru.
- Implementuje `encoding.TextMarshaler`/`TextUnmarshaler`/`TextAppender`, takže se bez problémů vejde do JSON struktur.
- **`NewV7` je ten, který chcete pro primární klíče databáze.** Časové uspořádání znamená, že se váš B-strom index přestane fragmentovat, což je u UUID v4 známý problém.

Nyní můžete odebrat jednu závislost. Pokud potřebujete v1/v3/v5 nebo pokročilejší možnosti parsování, externí knihovny mají stále své místo.

### 3.3. crypto/mldsa: postkvantové podpisy, a jsou obrovské

Go 1.24 nám dalo `crypto/mlkem` pro postkvantovou výměnu klíčů. Go 1.27 přináší druhou polovinu: podpisy ML-DSA, standardizované 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
```

Podívejte se na ta čísla. **Jediný podpis má 3 309 bajtů.** Ed25519 má 64. Je to padesátinásobný nárůst a veřejný klíč má k tomu téměř 2 KB. Vložte několik takových podpisů do řetězce certifikátů a váš TLS handshake začne potřebovat vlastní strategii MTU.

To je skutečná cena kvantové odolnosti dnes a proto zatím nikdo nepřechází na všechno hned zítra. Ale je to již ve standardní knihovně, což je přesně to, co chcete, _dříve_ než to budete potřebovat.

`Options.Context` slouží k oddělení domén: podepisujte stejným klíčem pro různé účely, pro každý použijte jiný kontext, a podpis z jednoho kontextu neprojde ověřením v jiném. Příklad výše to přesně ukazuje: stejný klíč, stejná zpráva, jiný kontext, odmítnuto.

`PrivateKey` implementuje `crypto.Signer`, takže zapadne do existujících rozhraní. `crypto/x509` zpracovává klíče a podpisy ML-DSA a `crypto/tls` podporuje schémata podpisů `MLDSA44`/`MLDSA65`/`MLDSA87` v TLS 1.3.

Existuje také `SignDeterministic`, který přeskakuje náhodnost, což se hodí pro testy a reprodukovatelné buildy.

### 3.4. simd: vektorové instrukce bez assembleru

Go 1.26 zavedlo experimentálně architekturně specifický `simd/archsimd`. Go 1.27 přidává `simd`, které je **přenositelné a nezávislé na šířce vektoru**. Zapíná se pomocí `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:])
	}
	// zpracovat zbytek pomocí částečného načtení/uložení — bez skalární úklidové smyčky
	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]
```

Rozhodnutí o návrhu, na kterém záleží: **šířka vektoru není nikdy pevně zakódována.** Zavoláte `va.Len()` za běhu. `VectorBitSize()` vám řekne skutečnou šířku, `Emulated()` vám řekne, zda máte skutečný hardware, nebo zdvořilou softwarovou imitaci. Můj Mac hlásil 128bitové NEON; stroj s AVX-512 hlásí více; stroj bez podpory hlásí emulaci a kód přesto běží.

Zvláštní zmínku zaslouží `LoadFloat32sPart`/`StorePart`. Nejotravnější část ručně psaného SIMD je vždy nepravidelný konec pole a toto ho zvládne bez samostatné skalární smyčky.

Stále experimentální, API stále nestabilní, nenasazujte do produkce. Ale skutečnost, že jde o kód Go, a ne o assembler nebo cgo, je opravdu velká věc.

### 3.5. hash/maphash.Hasher

Nové rozhraní popisující kontrakt mezi hodnotovým typem a kontejnery založenými na hashování:

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

Proč? Vestavěná mapa v Go přijímá pouze klíče `comparable`. Nemůžete použít slice jako klíč. Nemůžete definovat „rovnost bez ohledu na velikost písmen“. `Hasher` řeší obojí najednou:

```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)   // stejný seed, jinak celé toto nedává smysl
	hr.Hash(&h, v)
	return h.Sum64()
}

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

Pro běžnou sémantiku `==` existuje `ComparableHasher[T]`:

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

Háček: **`Hasher` je rozhraní, nikoli datová struktura.** Zatím neexistuje standardní hash tabulka ani Bloomův filtr, který by jej používal. Jde o základ pro budoucí balíček kontejnerů. Dnes byste jej použili při stavbě vlastní struktury, nebo byste využili existující implementaci, například `go/types.Hasher` (která umožňuje použít `types.Type` jako klíč mapy s respektováním `Identical`).

Správa seedu je na vás. Kdyby `hashOf` výše vytvářela při každém volání nový seed, stejné hodnoty by se hashovaly odlišně a příklad by vypsal `false`. To je přesně chyba, kterou jsem udělal při prvním pokusu. Jeden seed na jeden kontejner. (Seed je náhodný, aby se zabránilo útokům DoS záplavou hashů, a proto to není pouhá konstanta.)

### 3.6. httptest.NewTestServer + synctest.Sleep: úder pro spáče

Pokud z tohoto vydání přijmete jen jednu věc, ať je to tato.

`testing/synctest` vyšlo z experimentu v Go 1.25 a dalo nám falešné hodiny pro testování souběžného kódu. Jakmile však váš test sáhl na skutečnou síť, iluze se zhroutila. `httptest.NewTestServer` v Go 1.27 používá **paměťovou falešnou síť**, takže bublina zůstává neporušená. A `synctest.Sleep` (= `time.Sleep` + `synctest.Wait`) to doplňuje.

Zde je test opakování s exponenciálním 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 by měl být přesně 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)
```

Přečtěte si to dvakrát. Simulovalo tři sekundy backoffu proti skutečnému HTTP serveru a skončilo za **0,00 sekundy**. A tvrzení zní `elapsed == 3*time.Second`, tedy _přesně_ tři sekundy, nikoli „alespoň tři sekundy, s ohledem na jitter plánovače“. Falešné hodiny žádný jitter nemají.

`synctest.Sleep` existuje z konkrétního důvodu: pokud váš test spí stejně dlouho jako testovaný kód, je to otázka štěstí, který se probudí první. `synctest.Sleep` spí _a potom_ čeká, dokud každá další goroutina v bublině není trvale blokována, takže pozorujete systém poté, co se ustálil.

Timeouty, opakování, circuit breakery, rate limitery: každý časově závislý test HTTP klienta, který vlastníte, se může stát rychlým a deterministickým. Pokud vaše testovací sada dnes drží pohromadě díky `time.Sleep(100 * time.Millisecond)` a naději, toto je vaše cesta ven.

### 3.7. Změny net/http, které skutečně ovlivní produkci

Tiché, ale projeví se ve vašich metrikách.

**Tělo HTTP/1 odpovědí se při Close automaticky vyprázdní.** Nepřečtený obsah je nyní při zavření těla vyprázdněn (až do konzervativního limitu), takže spojení lze znovu použít. To znamená, že konečně můžete smazat tuto formulku, kterou si od roku 2016 všichni kopírují z jedné odpovědi na Stack Overflow:

```go
// již není nutné
defer func() {
	io.Copy(io.Discard, resp.Body)
	resp.Body.Close()
}()
```

Pro většinu programů jde o nic nedělající operaci nebo malý zisk. Pokud to věci zhorší, pravděpodobně jste v kategorii, kterou poznámky k vydání zdvořile popisují: `Transport.MaxIdleConns` nastaveno na 0, nebo nový `Client` na každý požadavek, což zcela obchází limit nečinných spojení. `Transport.DisableKeepAlives = true` to přikryje, ale skutečná rada z poznámek k vydání zní, že „hlubší pohled by pravděpodobně byl přínosný“, což v Go-týmovém jazyce znamená _máte větší problémy_.

**Priorita klienta HTTP/2 (RFC 9218).** Server nyní respektuje signály priority klienta. Pokud dáváte přednost staršímu plánování round-robin, nastavte `Server.DisableClientPriority = true`.

**`Server.MaxHeaderValueCount`.** Omezuje, kolik hodnot hlaviček server přijme; výchozí hodnotou je `DefaultMaxHeaderValueCount`. Další dveře zavřeny pro „pošli deset tisíc hlaviček a sleduj, co se stane“.

**ALPN na uživatelem poskytnutých spojeních.** Pokud vaše `net.Conn` implementuje `ConnectionState() tls.ConnectionState`, `Transport` a `Server` na něm provedou vyjednávání TLS ALPN, takže vlastní dialery procházející proxy mohou stále vyjednat HTTP/2.

### 3.8. Drobnosti, které přinášejí radost

**`strings.CutLast` / `bytes.CutLast`.** `Cut` dělí na _prvním_ oddělovači. Varianta „poslední“ neexistovala, takže každý ručně psal `LastIndex` a řezání, a zhruba 30 % z nás se napoprvé spletlo o jedna.

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

Skvělé pro přípony souborů a pro parsování `host:port`, kde musíte najít _poslední_ dvojtečku, protože IPv6 adresy jich obsahují plno.

**`math/big.Int.Divide`.** Dělení s explicitním režimem zaokrouhlování, které nahrazuje hod mincí otázkou, zda chcete `Quo`, nebo `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
```

Pokud pracujete v oboru, kde je pravidlo zaokrouhlování zapsáno v předpisu, je toto pro vás.

**`url.URL.Clone` a `url.Values.Clone`.** Hluboké kopie. Staré mělké kopírování `*u` sdílelo ukazatel `Userinfo`, což vedlo k přesně tomu typu chyby, jejíž nalezení zabere den a oprava pět 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 čtení `net.UnixConn`** nyní vracejí `io.EOF` přímo, místo aby jej zabalily do `net.OpError`. `err == io.EOF` nyní funguje, jak to vždy mělo.

**`database/sql.ConvertAssign` a `driver.RowsColumnScanner`.** Funkce pro autory ovladačů. První zpřístupňuje konverze typů, které provádí `Rows.Scan`; druhý umožňuje ovladačům skenovat přímo do uživatelských cílů a přeskočit mezilehlou alokaci.

**`unicode` 15 → 17.** Dvě verze najednou. Chování klasifikace a normalizace řetězců se může jemně změnit. Pokud máte testy, které na tom závisí, zjistíte to.

**`compress/flate` je rychlejší**, a **jeho výstupní bajty se mohou lišit od Go 1.26.** Projeví se to i v `archive/zip`, `compress/gzip`, `compress/zlib` a `image/png`. Pokud máte golden-file testy, které hashují komprimovaný výstup, rozbijí se, a nebude to regrese. Zkontrolujte to _před_ upgradem, ne během vyšetřování incidentu.

## 4. Toolchain

### 4.1. go fix získal další modernizátory

`go fix` se tiše mění v nástroj pro modernizaci kódu. Go 1.27 přidává `atomictypes`, `embedlit`, `slicesbackward` a `unsafefuncs`. Použijte `-diff` pro náhled:

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

Na jednom malém souboru se spustili čtyři modernizátory:

- `embedlit`: přepisuje vložené literály na novou syntaxi z §1.2
- `atomictypes`: `atomic.AddInt64(&x, 1)` se mění na `atomic.Int64.Add(1)`, což znemožňuje nesynchronizovaný přístup a tiše opravuje chyby zarovnání na 32bitových platformách, o kterých jste nevěděli
- `slicesbackward`: zpětné smyčky se mění na `slices.Backward`
- `waitgroupgo`: rituál `Add`/`go`/`Done` se mění na `wg.Go` (přejmenováno z `waitgroup` z 1.26, aby nedocházelo k nejednoznačnosti)

`go tool fix help` vypíše všech 26; `go tool fix help <name>` vysvětlí jeden z nich. Také `fmtappendf` byl odstraněn „z důvodu stylistických obav“, což je nádherně diplomatický způsob, jak popsat, co se stalo v tom vlákně issue.

Spuštění `go fix ./...` na starší kódové základně je opravdu uspokojivé odpoledne. Nejprve si samozřejmě přečtěte diff.

### 4.2. go test ve výchozím stavu spouští stdversion

Toto je změna, která pravděpodobně přeruší váš den.

`go test` nyní ve výchozím stavu spouští vet kontrolu `stdversion`, která označuje symboly standardní knihovny novější, než povoluje direktiva `go` ve vašem `go.mod`:

```go
// go.mod říká: go 1.24
package sv

import "strings"

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

Tím se odstraňuje klasický scénář, kdy vše funguje na vašem stroji (nejnovější toolchain) a v CI nebo na starším prostředí uživatele to exploduje. Pokud publikujete knihovny s konzervativní direktivou `go`, je to dárek.

### 4.3. go test -json získal OutputType

Řádky `"Action":"output"` nyní nesou volitelné pole `"OutputType"`: `"error"`, `"error-continue"` nebo `"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'
```

Výstup `t.Log` (bez pole), výstup `t.Error` (`error`) a řádky generované frameworkem (`frame`) jsou nyní rozlišitelné. Kdokoli, kdo napsal regulární výraz, aby zjistil, které řádky výstupu testu jsou „skutečné“, si jej nyní může smazat.

### 4.4. Vylepšení go doc

**Syntaxe `package@version`.** Přečtěte si dokumentaci konkrétní verze, aniž byste ji přidali do svého modulu:

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

Užitečné pro kontrolu změn před upgradem.

**`-ex` a výpis zdrojového kódu příkladů:**

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

Zdrojový kód příkladu _i_ očekávaný výstup, přímo v terminálu, bez otevírání záložky prohlížeče, která bude stále otevřená příští čtvrtek.

### 4.5. go mod tidy uklízí vaše bloky require

U modulů s `go 1.27` nebo novějším `go mod tidy` slučuje roztroušené bloky `require` nejvýše do dvou (přímé a nepřímé) a zachovává připojené komentáře.

```go
// Před
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 (
	// sítě
	golang.org/x/net v0.33.0
	golang.org/x/sync v0.10.0
)
```

Všimněte si, že komentář `// networking` přežil. Toto především uklízí po řešení **konfliktů při slučování v Gitu**, kde se roztroušené bloky `require` rodí. Pokud má váš tým více než tři lidi přidávající závislosti, konflikty ve vašem `go.mod` budou výrazně tišší.

### 4.6. Různé drobnosti

- **Podpora `bzr` byla odstraněna.** Pokud se vás to týká, rád bych si poslechl váš příběh.
- **`go tool trace -http` se při zadání pouze portu váže na localhost.** `-http=:6060` již neposlouchá na všech rozhraních. Použijte `-http=0.0.0.0:6060`, pokud to myslíte vážně. Nyní to odpovídá `go tool pprof` a brání občasnému náhodnému veřejnému profileru.
- **Soubory s odpověďmi (`@file`)** jsou podporovány v `compile`, `link`, `asm`, `cgo`, `cover` a `pack` ve formátu kompatibilním s GCC. Pro buildovací systémy, které překračují limity délky příkazového řádku, zdar, Bazel.

## 5. Kompilátor, linker, porty

**Kompilátor.** Relativní názvy souborů v direktivách `//line` se nyní vyhodnocují vůči adresáři obsahujícího souboru, v souladu s `go/scanner`. Relevantní, pokud píšete generátory kódu.

Názvy symbolů funkčních literálů (closures) jsou nyní také jednodušší: stejný název bez ohledu na inlining a více instancí stejného literálu může sdílet kód v binárním souboru. Žádná funkční změna, _až na_ jednu: kód, který porovnává identitu funkcí pomocí `reflect.Value.Pointer`, uvidí „rovno“ častěji než dříve. Toto porovnání nikdy nebylo platné, ale pokud ho máte, nyní začne lhát hlasitěji.

**Linker.** Nové volby `-macos` a `-macsdk` nastavují verze OS a SDK v příkazu načtení `LC_BUILD_VERSION` pro macOS.

**Porty.**

- **Darwin nyní vyžaduje macOS 13 Ventura nebo novější**, jak bylo oznámeno v 1.26. Zkontrolujte své CI runnery.
- **PowerPC (`GOOS=linux GOARCH=ppc64`) přešlo na ABI ELFv2.** Vyžaduje jádro Linuxu 3.13+ (RHEL7 má zpětný port do 3.10). Cgo, PIE a externí linkování jsou nyní podporovány. Pokud používáte cgo, ale potřebujete statický čistě Go binární soubor, nastavte `CGO_ENABLED=0`.

## 6. Kontrolní seznam pro upgrade

Go 1.27 bere kompatibilitu vážně, ale před zvýšením verze zkontrolujte toto:

- [ ] **Golden-file testy na komprimovaném výstupu.** `compress/flate` změnil enkodéry; bajty gzip/zip/png se mohou lišit.
- [ ] **Testy porovnávající řetězce chybových hlášení JSON.** Chování je identické; **text chyby identický není.**
- [ ] **Odstraněné GODEBUGy v `go.mod`.** `asynctimerchan`, `gotypesalias`, `tlsrsakex`, `tls3des`, `tls10server`, `tlsunsafeekm`, `x509keypairleaf`: tyto selžou při buildu pouze tehdy, když jsou nastaveny na své _staré_ hodnoty.
- [ ] **Porušení `stdversion`.** `go test` je nyní zachytí. Pokud udržujete knihovnu s konzervativní direktivou `go`, spusťte to včas.
- [ ] **CI runnery s macOS 12 nebo starším.** Podpora skončila.
- [ ] **Testy ověřující názvy symbolů funkčních literálů** a **porovnání funkcí přes `reflect.Value.Pointer`.**
- [ ] **`Transport.MaxIdleConns = 0`** nebo `Client` na každý požadavek. V kombinaci s automatickým vyprazdňováním těl odpovědí se to může zpomalit.

## Závěrečné myšlenky

Go 1.27 je vydání, kde se najednou splatilo mnoho odložené údržby.

Generické metody byly chybějícím kouskem práce na generikách z 1.18. Selektory polí ve struct literálech uzavřely jedenáctiletý issue. `encoding/json/v2` představuje dva roky diskuse o návrhu. A `uuid` zaplnilo díru, kterou téměř každý Go projekt celou dekádu латal stejnou externí závislostí.

Pokud chcete prioritní pořadí, jak z toho skutečně něco získat:

1. **`httptest.NewTestServer` + `synctest.Sleep`**: časově závislé HTTP testy se stanou rychlými a deterministickými. Nejlepší poměr úsilí a přínosu v celém vydání.
2. **Profil úniku goroutin**: vystavte jeden endpoint ve stagingu a připravte se na pokoru.
3. **Štítky goroutin v tracebackách**: tři řádky `pprof.Do` na vstupním bodu požadavků promění vaši další stack dump ve 3 ráno v něco čitelného.
4. **`go fix ./...`**: nechte nástroj, ať provede modernizaci.
5. **`encoding/json/v2`**: nespěchejte. Přijměte jednu option po druhé.

Stáhněte si `go1.27rc3` a spusťte na něm svou testovací sadu hned teď. Vše na tomto kontrolním seznamu je výrazně příjemnější objevit v úterý odpoledne než během incidentu.

**Reference**

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