---
id: 1a31e06c0d969b4b6cbbc590259cf089
author: iwanhae
title: Go 1.27, atau Bagaimana Saya Belajar Berhenti Khawatir dan Mencintai Generic Methods
description: Metode generik, encoding/json/v2, paket uuid standar, dan detektor kebocoran goroutine yang benar-benar berfungsi. Sebuah tur Go 1.27 dengan contoh-contoh yang semuanya telah benar-benar dijalankan.
language: id
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
---

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

Go 1.27 akan dirilis pada Agustus 2026, dan untuk kali ini bahasa pemrogramannya sendiri benar-benar berubah. Bukan sekadar "kami menambahkan fungsi baru ke `slices`", melainkan perubahan yang sesungguhnya. Generic methods sudah hadir. Sebuah isu berumur sebelas tahun akhirnya ditutup. `encoding/json` diganti secara diam-diam dengan mesin baru ketika pesawat sedang mengudara.

Setiap contoh di bawah ini dijalankan pada `go1.27rc3` (darwin/arm64). Setiap blok output adalah output sungguhan, disalin apa adanya, termasuk pesan error. Jika ada yang terlihat janggal, itu karena memang demikianlah yang dikeluarkan oleh compiler.

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

## 1. Bahasa Pemrograman Telah Berubah (Ya, Sungguh)

### 1.1. Generic Methods

Sejak Go 1.18 kita sudah memiliki generics, dan sejak Go 1.18 pula kita memiliki Percakapan Itu. Biasanya alurnya seperti ini:

> "Biar saya tulis saja method `Map` pada tipe slice saya..."
>
> `method must have no type parameters`
>
> "...baiklah. Kalau begitu, fungsi level paket saja."

Method hanya dapat menggunakan type parameter yang dideklarasikan oleh _receiver_. Method yang kita tulis sendiri tidak dapat memperkenalkan type parameter baru. Akibatnya, setiap transformasi generik harus diasingkan ke level paket, tempat ia berdampingan dengan tujuh belas helper lepas lainnya yang namanya merupakan variasi dari `MapSlice`.

Go 1.27 memperbaiki hal ini:

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

// F adalah type parameter yang dideklarasikan oleh method itu sendiri
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 bahkan tidak perlu bersifat generik. Sebuah struct biasa dapat memiliki method generik:

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

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

Sekarang, sebelum Anda merefaktor seluruh codebase pada sore ini, perhatikan **dua batasan**, dan keduanya lebih penting daripada kelihatannya:

1. Method interface tidak boleh mendeklarasikan type parameter.
2. Generic methods tidak dapat mengimplementasikan method interface.

Batasan kedua inilah yang akan merugikan Anda:

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

Jadi, generic methods dan dynamic dispatch tidak dapat dicampur. Ini bukan karena tim Go bersikap pelit, melainkan karena sebuah generic method memiliki jumlah instansiasi yang tak terhingga, sedangkan tabel method interface harus berhingga dan diketahui pada waktu kompilasi. Anda tidak dapat memasukkan sesuatu yang tak terhingga ke dalam tabel yang berhingga. Semesta sudah berkata tidak.

Kesimpulan praktisnya: generic methods diperuntukkan bagi tipe konkret dengan API bergaya utilitas. Sesuatu yang harus disembunyikan di balik interface tetap memerlukan pendekatan lama.

Standard library sudah memanfaatkan fitur ini. `Rand` pada `math/rand/v2` memperoleh sebuah generic method:

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

Sebelumnya hanya fungsi level paket `rand.N` yang bersifat generik, yang berarti hanya dapat menggunakan sumber global. Sekarang `*Rand` berseed milik Anda sendiri memperoleh kemudahan yang sama. Perubahan kecil. Perubahan yang menyenangkan.

### 1.2. Pemilih Field pada Struct Literal, atau: Issue #9859 Akhirnya Terselesaikan

Embedding memberi Anda `u.ID`. Embedding _tidak_ memberi Anda `User{ID: 1}`. Sebaliknya, embedding memberi Anda `User{Base: Base{ID: 1}}`, yang merupakan cara Go menanyakan apakah Anda benar-benar menghendakinya.

[Issue #9859](https://go.dev/issue/9859) diajukan pada tahun 2015. Kini issue tersebut telah ditutup. Di suatu tempat, seorang gopher yang sejak itu telah dua kali berganti karier sedang menerima notifikasi GitHub.

```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: gunakan field hasil promosi secara langsung
line := Line{name: "diagonal", q: Point3D{y: -4, z: 12.3}}
```

`name` bukanlah field dari `Line`. Ia adalah field dari `Object` yang di-embed. Sebelumnya Anda harus menulis `Line{Object: Object{name: "diagonal"}, ...}`.

Sekarang untuk catatan kaki yang penting, yang saya periksa dengan benar-benar mencoba mengompilasi semuanya.

**(a) Kunci tetap berupa identifier biasa.** Anda tidak dapat menulis path selector yang sembarang.

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

Jadi, fitur ini bukanlah "kunci kini bekerja seperti akses field". Fitur ini secara spesifik adalah "nama field hasil promosi secara implisit diizinkan". Cakupannya sedikit lebih sempit daripada yang tersirat dari judulnya.

**(b) Anda tidak dapat menentukan sebuah field embedded dan sebuah field hasil promosi dari field tersebut secara bersamaan.**

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

Masuk akal. Anda memberi tahu compiler dua hal yang saling bertentangan mengenai memori yang sama, dan compiler menolak untuk menebak.

**(c) Embedding pointer tidak dianjurkan.**

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

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

Untuk mengikuti sebuah pointer, Anda memerlukan pointer untuk diikuti, dan pada saat konstruksi literal pointer tersebut belum ada. Ini juga masuk akal.

Kabar baiknya: `go fix` akan menulis ulang literal lama Anda secara otomatis. Lebih lanjut mengenai hal itu nanti.

### 1.3. Inferensi Tipe Fungsi Menjadi Tidak Semena-mena

Inferensi tipe untuk fungsi generik dahulu berfungsi di sebagian tempat dan tidak di tempat lain, tanpa prinsip yang dapat dikenali di balik perbedaan tersebut. Menugaskan ke sebuah variabel? Berfungsi. Memasukkan fungsi yang sama persis ke dalam field struct? Error compiler, silakan tulis `double[int]` seolah-olah kita masih di tahun 2022.

Go 1.27 membuat inferensi bekerja di **setiap konteks ketika tipe target diketahui secara tidak ambigu**:

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

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

	var fn func(float64) float64 = double  // ini selalu berfungsi

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

Satu `double`, diinstansiasi sebagai `func(int) int` di tiga tempat dan `func(float64) float64` di tempat keempat, sepenuhnya dari konteks. Jika Anda menulis struct opsi atau tabel handler yang penuh dengan nilai fungsi, sejumlah noise `[T]` akan segera hilang dari codebase Anda.

## 2. Runtime: Performa Gratis dan Detektor Kebocoran

### 2.1. Alokasi Terspesialisasi Ukuran

Compiler kini mengeluarkan pemanggilan ke rutin alokasi yang terspesialisasi ukuran untuk objek kecil. Release notes mengklaim pengurangan alokasi hingga 30% untuk objek di bawah 80 byte, dan sekitar 1% secara keseluruhan untuk program yang sangat intensif dalam alokasi.

Saya tidak mempercayai release notes tanpa pengukuran, maka:

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

```
# default (size-specialized malloc aktif)
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

```

Lebih cepat 30–35% dalam sebuah microbenchmark yang tidak melakukan apa pun selain alokasi pada Apple Silicon. Dengan kata lain: ini adalah kasus terbaik, dicapai dalam kondisi laboratorium oleh benchmark yang dirancang untuk membuat angka tersebut tampak bagus. Dalam program nyata, GC dan pekerjaan sesungguhnya akan menenggelamkan sebagian besar keuntungan tersebut. Klaim sekitar 1% adalah klaim yang jujur.

Harganya berupa ukuran binary. Hello World:

```
2413202 bytes  (default)
2361826 bytes  (nosizespecializedmalloc)
```

Sekitar 50KB, tetap, tanpa memandang program Anda. Anda dapat menonaktifkannya dengan `GOEXPERIMENT=nosizespecializedmalloc`, tetapi **jalan keluar tersebut dijadwalkan untuk dihapus pada Go 1.28**, sehingga perlakukan ia sebagai solusi sementara untuk laporan bug, bukan sebagai gaya hidup.

### 2.2. Goroutine Leak Profile Kini Benar-benar Tersedia

Bersifat eksperimental pada Go 1.26, dan tersedia secara umum pada 1.27. GOEXPERIMENT `goroutineleakprofile` telah dihapus; fitur ini langsung berfungsi.

```go
func leak() {
	ch := make(chan int) // tidak akan pernah ada yang mengirim. tidak akan pernah.
	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
```

Tiga goroutine, terjebak secara permanen, dengan nama file dan nomor baris yang menunjuk tepat ke lokasi di mana Anda membuatnya.

Trik di balik fitur ini sangat cerdas: ia **menggunakan ulang analisis keterjangkauan (reachability) milik garbage collector**. Jika goroutine G diblokir pada primitif P, dan P tidak dapat dijangkau dari goroutine mana pun yang dapat dijalankan (atau dari apa pun yang dapat dibangunkan oleh goroutine tersebut), maka tidak ada yang dapat menyentuh P lagi, sehingga G tidak akan pernah terbangun. Ini bukan heuristik, melainkan sebuah pembuktian.

Desain yang sama juga memberikan batasannya secara gratis: jika channel atau mutex dapat dijangkau melalui variabel global, atau melalui variabel lokal dari suatu goroutine yang masih berjalan, GC tetap dapat melihatnya, sehingga runtime tidak dapat menyimpulkan apa pun. Perhatikan bahwa bahkan contoh sederhana di atas memerlukan dua pemanggilan `runtime.GC()` agar dapat dilaporkan. Fitur ini tidak akan menangkap semuanya.

Namun, fitur ini akan menangkap kasus klasik "lupa membatalkan context, goroutine worker hidup selamanya", yang merupakan sebagian besar kebocoran dalam kebanyakan kasus.

Jika Anda mengimpor `net/http/pprof`, profil ini juga tersedia di `/debug/pprof/goroutineleak`. Pasang endpoint tersebut di staging, periksa setiap minggu, dan rasakan kengerian yang diam-diam.

Pekerjaan ini dikontribusikan oleh Vlad Saioc dari Uber, yang layak mendapatkan minuman pilihannya sendiri.

### 2.3. Traceback Kini Memberi Tahu Anda Request Mana yang Gagal

Untuk modul yang mendeklarasikan Go 1.27 atau yang lebih baru, baris header traceback kini menyertakan goroutine label dari `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}:
...
```

Detail yang penting: **label diwarisi oleh goroutine anak.** Goroutine 3 tidak pernah menetapkan label. Ia menerimanya dari induknya.

Jadi, ketika production mengalami deadlock berikutnya dan seseorang mengirim SIGQUIT ke proses tersebut, alih-alih 4.000 goroutine yang semuanya tampak identik, Anda akan mendapatkan 4.000 goroutine yang diberi tag request dan tenant mana yang menjadi miliknya. Tiga baris `pprof.Do` pada titik masuk request Anda sudah cukup untuk mendapatkan hal tersebut.

Karena label dapat berisi hal-hal yang tidak ingin Anda tampilkan ke stderr, `GODEBUG=tracebacklabels=0` akan menonaktifkannya, dan opsi nonaktif ini secara eksplisit dimaksudkan untuk tetap ada selamanya.

### 2.4. asynctimerchan Telah Dihapus Selamanya

Go 1.23 membuat channel timer menjadi unbuffered (sinkron) dan menyediakan `asynctimerchan=1` sebagai cara untuk kembali ke perilaku lama. Pada 1.27, pengaturan tersebut **dihapus secara permanen**. Channel pada paket `time` bersifat sinkron, titik, tanpa negosiasi.

Bagian yang menarik adalah kebijakan yang diperkenalkan bersamaan dengan perubahan ini. GODEBUG yang sudah dihapus namun masih tertinggal di `go.mod` Anda tidak akan secara otomatis merusak build. Ia hanya akan merusak build jika diatur ke nilai **lama**:

```
# go.mod berisi: 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 berisi: godebug asynctimerchan=0
(build berhasil)
```

Artinya, satu-satunya pihak yang akan diperingatkan adalah mereka yang benar-benar bergantung pada perilaku yang telah dihapus. Semua orang yang mengatur nilai tersebut ke nilai default akhirnya dan kemudian melupakannya tetap dapat terus tidak memikirkannya. Ini adalah bagian arkeologi API yang bijaksana.

## 3. Standard Library

### 3.1. encoding/json/v2: Mesin Diganti di Tengah Penerbangan

Ini adalah perubahan besar. [Diskusi proposal selama lebih dari dua tahun](https://go.dev/issue/71497) akhirnya terealisasi.

Kini terdapat **tiga paket**, dan memahami pembagiannya adalah sebagian besar dari perjuangan ini:

| Paket                    | Fungsi                                                                          |
| ------------------------ | ------------------------------------------------------------------------------- |
| `encoding/json`          | API v1 yang sudah Anda kenal. **Perilaku 100% tidak berubah.** Kini diimplementasikan di atas v2 |
| `encoding/json/v2`       | Pemrosesan semantik. Nilai Go ↔ JSON                                            |
| `encoding/json/jsontext` | Pemrosesan sintaksis. JSON sebagai aliran token                                 |

Inti dari perubahan ini adalah bahwa `encoding/json` dibangun ulang di atas implementasi yang sepenuhnya berbeda dan **berperilaku identik**. Caranya sebagai berikut:

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

Setiap keunikan legacy v1 dikodekan sebagai sebuah opsi, dikemas dalam `DefaultOptionsV1()`, dan API v1 selalu menerapkan paket tersebut. Artinya, kode berikut berperilaku sama di 1.26 dan 1.27:

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

Ya, v1 masih diam-diam menerima key duplikat dan mengambil yang terakhir. Hal itu selalu terjadi dan masih terjadi hingga kini. Kompatibilitas berarti kompatibel juga dengan bagian yang buruk.

**Sementara itu, v2 memiliki pendapatnya sendiri:**

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

Key duplikat dan UTF-8 yang tidak valid ditolak. Keduanya benar-benar berbahaya, bukan sekadar berantakan: ketika dua parser tidak sepakat mengenai key duplikat mana yang menang, muncullah bug keamanan. CVE-2017-12635 pada CouchDB adalah contoh persis dari hal ini: sebuah body JSON dengan dua key `roles`, di mana validator membaca yang satu dan lapisan penyimpanan membaca yang lain. Menolak adalah keputusan yang tepat.

Opsi bersifat variadic:

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

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

// tolak field yang tidak dikenal, sebelumnya memerlukan Decoder dan 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`: sebagian besar hal yang sebelumnya Anda selesaikan dengan library pihak ketiga atau `MarshalJSON` yang ditulis tangan kini tersedia sebagai flag.

Dan bagian terbaik adalah cerita migrasinya. **Opsi yang datang belakangan akan menang**, sehingga Anda dapat mengadopsi ketatnya v2 satu perilaku demi satu perilaku:

```go
// pertahankan semantik v1, tetapi tolak key duplikat seperti yang dilakukan v2
jsonv2.Unmarshal(data, &v,
	json.DefaultOptionsV1(),
	jsontext.AllowDuplicateNames(false))
// duplicate object member name
```

Anda tidak perlu memindahkan codebase berukuran 200 ribu baris ke v2 hanya untuk berhenti menerima key duplikat. Anda cukup mengubah satu opsi. Untuk codebase yang besar, inilah jalur yang realistis.

Performa: marshaling kurang lebih setara, **unmarshaling jauh lebih cepat.** Jika terjadi masalah, `GOEXPERIMENT=nojsonv2` akan mengembalikan implementasi lama, dan opsi nonaktif tersebut pada akhirnya juga akan dihapus, jadi ajukan issue alih-alih terus bergantung padanya.

`jsontext` adalah lapisan tingkat rendah: `Encoder`/`Decoder` yang menelusuri JSON sebagai `Token` dan `Value` dengan state machine yang menjaga Anda tetap jujur. Gunakan ini ketika Anda menulis transformer streaming atau filter JSON dan tidak ingin mewujudkan nilai Go sama sekali.

### 3.2. Paket uuid Standar

Akhirnya. RFC 9562, berada di standard library, tanpa perlu `go get`.

```go
import "uuid"

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

	// v7: 48 bit teratas adalah timestamp, sehingga nilai ini selalu terurut berdasarkan waktu pembuatan
	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
}
```

Seluruh API terdiri dari `New`, `NewV4`, `NewV7`, `Nil`, `Max`, `Parse`, `MustParse`, dan satu tipe: `type UUID [16]byte`. Hanya itu. Anda dapat membaca seluruh dokumentasi paket sementara kopi Anda mendingin.

Detail yang layak diketahui:

- **`UUID` adalah `[16]byte`**, sehingga `==` dapat digunakan dan ia langsung dapat dipakai sebagai key map. Desainnya sama dengan `google/uuid`.
- **`Nil` dan `Max` adalah fungsi, bukan variabel.** Karena sebuah `var Nil UUID` di level paket adalah senjata berisi yang diarahkan ke kaki Anda sendiri, dan suatu saat seseorang di suatu tempat pasti akan menugaskan nilai ke variabel tersebut.
- Bit acak berasal dari generator yang **aman secara kriptografis**.
- Ia mengimplementasikan `encoding.TextMarshaler`/`TextUnmarshaler`/`TextAppender`, sehingga langsung cocok digunakan pada struct JSON.
- **`NewV7` adalah pilihan yang tepat untuk primary key basis data.** Pengurutan berdasarkan waktu berarti indeks B-tree Anda tidak lagi terfragmentasi, sesuatu yang terkenal menjadi kelemahan UUID v4.

Sekarang Anda dapat menghapus sebuah dependensi. Jika Anda membutuhkan v1/v3/v5 atau opsi parsing yang lebih canggih, library pihak ketiga masih memiliki perannya.

### 3.3. crypto/mldsa: Tanda Tangan Pasca-Kuantum, dan Ukurannya Sangat Besar

Go 1.24 memberi kita `crypto/mlkem` untuk pertukaran kunci pasca-kuantum. Go 1.27 membawa separuh lainnya: tanda tangan ML-DSA, yang distandarisasi sebagai 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
```

Perhatikan angka-angka tersebut. **Satu tanda tangan berukuran 3.309 byte.** Ed25519 berukuran 64. Itu peningkatan 50 kali lipat, dan public key-nya hampir 2KB di atasnya. Jika Anda menyisipkan beberapa tanda tangan ini ke dalam certificate chain, handshake TLS Anda akan memerlukan strategi MTU tersendiri.

Inilah biaya nyata dari ketahanan kuantum saat ini, dan inilah alasan mengapa belum ada yang beralih menggunakan semuanya besok. Namun, fitur ini sudah berada di standard library, dan di sanalah seharusnya ia berada _sebelum_ Anda membutuhkannya.

`Options.Context` adalah pemisahan domain: tandatangani dengan kunci yang sama untuk tujuan yang berbeda, gunakan context yang berbeda untuk masing-masing, dan tanda tangan dari satu context tidak akan dapat diverifikasi pada context lain. Contoh di atas menunjukkan hal itu dengan tepat: kunci yang sama, pesan yang sama, context yang berbeda, ditolak.

`PrivateKey` mengimplementasikan `crypto.Signer`, sehingga dapat langsung dipasang pada interface yang sudah ada. `crypto/x509` menangani kunci dan tanda tangan ML-DSA, dan `crypto/tls` mendukung skema tanda tangan `MLDSA44`/`MLDSA65`/`MLDSA87` pada TLS 1.3.

Terdapat juga `SignDeterministic`, yang melewati pembangkitan acak, berguna untuk pengujian dan build yang dapat direproduksi.

### 3.4. simd: Instruksi Vektor Tanpa Assembly

Go 1.26 memperkenalkan `simd/archsimd` yang spesifik untuk arsitektur sebagai eksperimen. Go 1.27 menambahkan `simd`, yang **portabel dan agnostik terhadap lebar vektor**. Aktifkan dengan `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:])
	}
	// tangani sisa elemen dengan load/store parsial, tanpa loop skalar tambahan
	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]
```

Pilihan desain yang penting: **lebar vektor tidak pernah di-hardcode.** Anda menanyakan `va.Len()` saat runtime. `VectorBitSize()` memberi tahu lebar sebenarnya, dan `Emulated()` memberi tahu apakah Anda mendapatkan perangkat keras sungguhan atau peniruan perangkat lunak yang sopan. Mac saya melaporkan NEON 128-bit; mesin AVX-512 melaporkan lebih lebar; mesin tanpa dukungan apa pun melaporkan emulasi dan kode tetap berjalan.

`LoadFloat32sPart`/`StorePart` layak mendapat perhatian khusus. Bagian paling menjengkelkan dari SIMD yang ditulis tangan selalu adalah sisa elemen di ujung array, dan fitur ini menanganinya tanpa loop skalar terpisah.

Masih bersifat eksperimental, API-nya masih tidak stabil, jangan gunakan di production. Namun, fakta bahwa ini adalah kode Go dan bukan assembly atau cgo merupakan hal yang sangat besar.

### 3.5. hash/maphash.Hasher

Sebuah interface baru yang mendeskripsikan kontrak antara tipe nilai dan container berbasis hash:

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

Mengapa? Map bawaan Go hanya menerima key yang `comparable`. Anda tidak dapat menggunakan slice sebagai key. Anda tidak dapat mendefinisikan "sama dengan mengabaikan huruf besar-kecil". `Hasher` menyelesaikan kedua masalah tersebut sekaligus:

```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)   // seed yang sama, atau semua ini tidak bermakna
	hr.Hash(&h, v)
	return h.Sum64()
}

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

Untuk semantik `==` yang biasa, tersedia `ComparableHasher[T]`:

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

Jebakannya: **`Hasher` adalah sebuah interface, bukan struktur data.** Belum ada tabel hash atau Bloom filter standar yang menggunakannya. Ini merupakan fondasi untuk paket container di masa depan. Saat ini Anda dapat menggunakannya ketika membangun struktur sendiri, atau mengonsumsi implementasi yang sudah ada seperti `go/types.Hasher` (yang memungkinkan Anda menggunakan `types.Type` sebagai key map, dengan menghormati `Identical`).

Pengelolaan seed menjadi tanggung jawab Anda. Jika `hashOf` di atas membuat seed baru pada setiap pemanggilan, nilai yang identik akan menghasilkan hash yang berbeda dan contoh tersebut akan mencetak `false`, yaitu bug yang persis saya tulis pada percobaan pertama saya. Satu seed untuk setiap container. (Seed diacak untuk menggagalkan serangan DoS hash-flooding, itulah sebabnya ia bukan sekadar konstanta.)

### 3.6. httptest.NewTestServer + synctest.Sleep: Pukulan Sleeper

Jika Anda hanya mengadopsi satu hal dari rilis ini, jadikan yang ini.

`testing/synctest` lulus dari status eksperimental pada Go 1.25 dan memberi kita jam palsu untuk menguji kode konkuren. Namun, begitu test Anda menyentuh jaringan sungguhan, ilusinya runtuh. `httptest.NewTestServer` pada Go 1.27 menggunakan **jaringan palsu dalam memori**, sehingga bubble tetap utuh. Dan `synctest.Sleep` (= `time.Sleep` + `synctest.Wait`) melengkapinya.

Berikut ini adalah test retry dengan exponential 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 seharusnya tepat 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)
```

Bacalah dua kali. Test ini mensimulasikan tiga detik backoff terhadap server HTTP sungguhan, dan selesai dalam **0,00 detik**. Dan assertion-nya adalah `elapsed == 3*time.Second`, _tepat_ tiga detik, bukan "setidaknya tiga detik, kurang lebih karena jitter scheduler". Jam palsu tidak mengalami jitter.

`synctest.Sleep` ada karena alasan yang spesifik: jika test Anda tidur selama durasi yang sama dengan kode yang diuji, siapa yang bangun lebih dulu adalah tebakan siapa pun. `synctest.Sleep` tidur _lalu_ menunggu hingga setiap goroutine lain di dalam bubble benar-benar terblokir secara permanen, sehingga Anda mengamati sistem setelah ia stabil.

Timeout, retry, circuit breaker, rate limiter: setiap test client HTTP yang bergantung pada waktu dan Anda miliki dapat menjadi cepat dan deterministik. Jika test suite Anda saat ini ditopang oleh `time.Sleep(100 * time.Millisecond)` dan harapan, inilah jalan keluar Anda.

### 3.7. Perubahan net/http yang Benar-benar Memengaruhi Production

Tenang, tetapi perubahan ini akan terlihat pada metrik Anda.

**Body response HTTP/1 dikosongkan otomatis saat Close.** Konten yang belum dibaca kini dikosongkan (hingga batas konservatif) ketika Anda menutup body, sehingga koneksi dapat digunakan kembali. Artinya, Anda akhirnya dapat menghapus mantra yang sudah disalin oleh semua orang dari jawaban Stack Overflow yang sama sejak 2016:

```go
// tidak lagi diperlukan
defer func() {
	io.Copy(io.Discard, resp.Body)
	resp.Body.Close()
}()
```

Bagi kebanyakan program, perubahan ini tidak berpengaruh atau memberikan sedikit keuntungan. Jika justru memperburuk keadaan, kemungkinan besar Anda berada dalam kondisi yang dijelaskan secara halus oleh release notes: `Transport.MaxIdleConns` diatur ke 0, atau `Client` baru untuk setiap request, sehingga melewati batas koneksi idle sepenuhnya. `Transport.DisableKeepAlives = true` dapat menutupinya, tetapi saran sebenarnya dari release notes adalah bahwa "pemeriksaan yang lebih mendalam kemungkinan akan bermanfaat", yang dalam bahasa tim Go berarti _Anda memiliki masalah yang lebih besar_.

**Prioritas klien HTTP/2 (RFC 9218).** Server kini menghormati sinyal prioritas dari klien. Jika Anda lebih menyukai penjadwalan round-robin lama, gunakan `Server.DisableClientPriority = true`.

**`Server.MaxHeaderValueCount`.** Membatasi berapa banyak nilai header yang akan diterima server, dengan nilai default `DefaultMaxHeaderValueCount`. Satu pintu lagi ditutup untuk skenario "kirim sepuluh ribu header dan lihat apa yang terjadi".

**ALPN pada koneksi yang disediakan pengguna.** Jika `net.Conn` Anda mengimplementasikan `ConnectionState() tls.ConnectionState`, `Transport` dan `Server` akan melakukan negosiasi TLS ALPN padanya, sehingga dialer kustom yang melewati proxy tetap dapat menegosiasikan HTTP/2.

### 3.8. Hal-hal Kecil yang Menyenangkan

**`strings.CutLast` / `bytes.CutLast`.** `Cut` memisahkan pada pemisah _pertama_. Tidak ada varian "terakhir", sehingga semua orang menulis sendiri `LastIndex` ditambah slicing, dan sekitar 30% dari kita melakukan off-by-one pada percobaan pertama.

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

Sangat berguna untuk ekstensi file dan untuk mengurai `host:port`, di mana Anda harus menemukan titik dua _terakhir_, karena alamat IPv6 penuh dengan titik dua.

**`math/big.Int.Divide`.** Pembagian dengan mode pembulatan yang eksplisit, menggantikan undian koin "apakah saya menginginkan `Quo` atau `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
```

Jika Anda bekerja di domain di mana aturan pembulatan tertulis dalam sebuah regulasi, fitur ini untuk Anda.

**`url.URL.Clone` dan `url.Values.Clone`.** Salinan dalam (deep copy). Salinan dangkal `*u` yang lama berbagi pointer `Userinfo`, yang menghasilkan jenis bug yang membutuhkan waktu sehari untuk ditemukan dan lima detik untuk diperbaiki.

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

Metode read pada **`net.UnixConn`** kini mengembalikan `io.EOF` secara langsung, alih-alih membungkusnya dalam `net.OpError`. `err == io.EOF` kini berfungsi, seperti seharusnya sejak awal.

**`database/sql.ConvertAssign` dan `driver.RowsColumnScanner`.** Fitur untuk penulis driver. Yang pertama mengekspos konversi tipe yang dilakukan oleh `Rows.Scan`; yang kedua memungkinkan driver melakukan scan langsung ke tujuan milik pengguna, melewati alokasi perantara.

**`unicode` 15 → 17.** Dua versi dalam satu lompatan. Perilaku klasifikasi dan normalisasi string dapat berubah secara halus. Jika Anda memiliki test yang bergantung padanya, Anda akan mengetahuinya.

**`compress/flate` menjadi lebih cepat**, dan **byte output-nya mungkin berbeda dari Go 1.26.** Hal ini berdampak pada `archive/zip`, `compress/gzip`, `compress/zlib`, dan `image/png`. Jika Anda memiliki golden-file test yang melakukan hash terhadap output terkompresi, test tersebut akan gagal, dan itu bukan regresi. Periksa hal ini _sebelum_ Anda melakukan upgrade, bukan saat meninjau insiden.

## 4. Toolchain

### 4.1. go fix Mendapatkan Lebih Banyak Modernizer

`go fix` diam-diam berkembang menjadi alat modernisasi kode. Go 1.27 menambahkan `atomictypes`, `embedlit`, `slicesbackward`, dan `unsafefuncs`. Gunakan `-diff` untuk melihat pratinjau:

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

Empat modernizer aktif pada satu file kecil:

- `embedlit`: menulis ulang literal embedded menggunakan sintaks baru dari §1.2
- `atomictypes`: `atomic.AddInt64(&x, 1)` menjadi `atomic.Int64.Add(1)`, yang membuat akses non-atomik menjadi tidak mungkin dan diam-diam memperbaiki bug alignment 32-bit yang tidak Anda ketahui keberadaannya
- `slicesbackward`: loop mundur menjadi `slices.Backward`
- `waitgroupgo`: ritual `Add`/`go`/`Done` menjadi `wg.Go` (diganti namanya dari `waitgroup` pada 1.26 untuk menghindari ambiguitas)

`go tool fix help` mencantumkan seluruh 26 modernizer; `go tool fix help <name>` menjelaskan satu per satu. Selain itu, `fmtappendf` dihapus "karena alasan gaya", yang merupakan cara yang sangat diplomatis untuk menggambarkan apa pun yang terjadi dalam thread issue tersebut.

Menjalankan `go fix ./...` pada codebase lama adalah sore hari yang sangat memuaskan. Tentu saja, baca diff terlebih dahulu.

### 4.2. go test Menjalankan stdversion secara Default

Ini adalah perubahan yang paling mungkin mengganggu hari Anda.

`go test` kini menjalankan pemeriksaan vet `stdversion` secara default, dengan menandai simbol standard library yang lebih baru daripada yang diizinkan oleh direktif `go` pada `go.mod` Anda:

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

import "strings"

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

Ini menghilangkan mode kegagalan klasik di mana semuanya berfungsi di mesin Anda (toolchain terbaru) tetapi meledak di CI atau di lingkungan lama pengguna. Jika Anda mempublikasikan library dengan direktif `go` yang konservatif, ini adalah hadiah.

### 4.3. go test -json Mendapatkan OutputType

Baris `"Action":"output"` kini membawa field opsional `"OutputType"`: `"error"`, `"error-continue"`, atau `"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 `t.Log` (tanpa field), output `t.Error` (`error`), dan baris yang dihasilkan framework (`frame`) kini dapat dibedakan. Siapa pun yang pernah menulis regex untuk mengetahui baris mana dari output test yang "nyata" kini dapat menghapusnya.

### 4.4. Peningkatan go doc

**Sintaks `package@version`.** Membaca dokumentasi untuk versi tertentu tanpa menambahkannya ke modul Anda:

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

Berguna untuk memeriksa apa yang berubah sebelum melakukan upgrade.

**`-ex` dan pencetakan source contoh:**

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

Source contoh _dan_ output yang diharapkan, di terminal, tanpa membuka tab browser yang masih akan terbuka pada Kamis depan.

### 4.5. go mod tidy Merapikan Blok require Anda

Untuk modul dengan `go 1.27` atau yang lebih baru, `go mod tidy` menggabungkan blok `require` yang tersebar menjadi paling banyak dua (direct dan indirect), sambil mempertahankan komentar yang melekat.

```go
// Sebelum
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
// Sesudah: go mod tidy
module tidydemo

go 1.27

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

Perhatikan bahwa komentar `// networking` tetap dipertahankan. Ini sebagian besar membereskan sisa **resolusi konflik merge Git**, tempat blok `require` yang tercecer biasanya lahir. Jika tim Anda beranggotakan lebih dari tiga orang yang menambahkan dependensi, konflik pada `go.mod` Anda akan menjadi jauh lebih sunyi.

### 4.6. Lain-lain

- **Dukungan `bzr` dihapus.** Jika ini memengaruhi Anda, saya dengan tulus ingin mendengar ceritanya.
- **`go tool trace -http` hanya terikat ke localhost** ketika hanya diberi port. `-http=:6060` tidak lagi mendengarkan di semua antarmuka. Gunakan `-http=0.0.0.0:6060` jika memang itu yang Anda maksud. Ini kini sejalan dengan `go tool pprof`, dan mencegah profiler publik yang tidak disengaja.
- **Response file (`@file`)** didukung oleh `compile`, `link`, `asm`, `cgo`, `cover`, dan `pack`, dalam format yang kompatibel dengan GCC. Untuk sistem build yang melampaui batas panjang command-line: halo, Bazel.

## 5. Compiler, Linker, Port

**Compiler.** Nama file relatif dalam direktif `//line` kini di-resolve terhadap direktori file yang memuatnya, sesuai dengan `go/scanner`. Relevan jika Anda menulis code generator.

Nama simbol function literal (closure) juga kini lebih sederhana: nama yang sama terlepas dari inlining, dan beberapa instance dari literal yang sama dapat berbagi kode di dalam binary. Tidak ada perubahan fungsional, _kecuali_: kode yang membandingkan identitas fungsi melalui `reflect.Value.Pointer` akan melihat "sama" lebih sering dari sebelumnya. Perbandingan itu memang tidak pernah valid, tetapi jika Anda memilikinya, inilah saatnya ia mulai berbohong dengan lebih lantang.

**Linker.** Opsi baru `-macos` dan `-macsdk` mengatur versi OS dan SDK pada load command `LC_BUILD_VERSION` macOS.

**Port.**

- **Darwin kini memerlukan macOS 13 Ventura atau yang lebih baru**, sebagaimana diumumkan pada 1.26. Periksa runner CI Anda.
- **PowerPC (`GOOS=linux GOARCH=ppc64`) beralih ke ABI ELFv2.** Memerlukan kernel Linux 3.13+ (RHEL7 dengan backport ke 3.10). Cgo, PIE, dan external linking kini didukung. Jika Anda menggunakan cgo tetapi memerlukan binary pure-Go yang statis, atur `CGO_ENABLED=0`.

## 6. Daftar Periksa Upgrade

Go 1.27 sangat serius dalam hal kompatibilitas, tetapi periksa hal-hal berikut sebelum Anda menaikkan versi:

- [ ] **Golden-file test pada output terkompresi.** `compress/flate` mengubah encoder-nya; byte gzip/zip/png mungkin berbeda.
- [ ] **Test yang mencocokkan string pesan error JSON.** Perilakunya identik; **teks error-nya tidak.**
- [ ] **GODEBUG yang dihapus di `go.mod`.** `asynctimerchan`, `gotypesalias`, `tlsrsakex`, `tls3des`, `tls10server`, `tlsunsafeekm`, `x509keypairleaf`: ini hanya menggagalkan build jika diatur ke nilai _lama_.
- [ ] **Pelanggaran `stdversion`.** `go test` kini mendeteksinya. Jalankan lebih awal jika Anda memelihara library dengan direktif `go` yang konservatif.
- [ ] **Runner CI macOS 12 atau yang lebih lama.** Dukungan sudah dihentikan.
- [ ] **Test yang memeriksa nama simbol function literal**, dan **perbandingan fungsi `reflect.Value.Pointer`.**
- [ ] **`Transport.MaxIdleConns = 0`** atau `Client` untuk setiap request. Dikombinasikan dengan pengosongan otomatis body response, hal ini mungkin menjadi lebih lambat.

## Pemikiran Penutup

Go 1.27 adalah rilis di mana banyak pemeliharaan yang tertunda jatuh tempo sekaligus.

Generic methods adalah bagian yang hilang dari pekerjaan generics 1.18. Pemilih field pada struct literal menutup issue berumur sebelas tahun. `encoding/json/v2` merupakan hasil dari dua tahun diskusi proposal. Dan `uuid` mengisi celah yang hampir setiap proyek Go tambal selama satu dekade dengan dependensi pihak ketiga yang sama.

Jika Anda menginginkan urutan prioritas untuk benar-benar memperoleh manfaat dari rilis ini:

1. **`httptest.NewTestServer` + `synctest.Sleep`**: test HTTP yang bergantung pada waktu menjadi cepat dan deterministik. Rasio usaha terhadap hasil terbaik di seluruh rilis ini.
2. **Goroutine leak profile**: ekspos satu endpoint di staging dan bersiaplah untuk direndahkan.
3. **Label goroutine pada traceback**: tiga baris `pprof.Do` pada titik masuk request Anda mengubah dump stack pukul 3 pagi berikutnya menjadi sesuatu yang dapat dibaca.
4. **`go fix ./...`**: biarkan alat ini melakukan modernisasi.
5. **`encoding/json/v2`**: tidak perlu terburu-buru. Adopsi satu opsi demi satu opsi.

Ambil `go1.27rc3` dan jalankan test suite Anda terhadapnya sekarang. Setiap item pada daftar periksa itu jauh lebih menyenangkan untuk ditemukan pada sore hari Selasa daripada saat insiden.

**Referensi**

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