GoSuda

Go 1.27, atau Bagaimana Saya Belajar Berhenti Khawatir dan Mencintai Generic Methods

By iwanhae
views ...

Tinjauan Go 1.27

Go 1.27 hadir pada Agustus 2026, dan untuk pertama kalinya bahasa itu sendiri mengalami perubahan. Bukan sekadar "kami menambahkan fungsi baru ke slices", melainkan perubahan yang sesungguhnya. Generic methods kini tersedia. Masalah yang telah berumur sebelas tahun akhirnya ditutup. encoding/json secara diam-diam digantikan dengan mesin baru saat sistem sedang beroperasi.

Setiap contoh di bawah ini dijalankan pada go1.27rc3 (darwin/arm64). Setiap blok keluaran adalah keluaran asli yang disalin dan ditempel, termasuk pesan galatnya. Jika ada sesuatu yang terlihat aneh, itu dikarenakan memang itulah yang disampaikan oleh kompilator.

1go install golang.org/dl/go1.27rc3@latest
2go1.27rc3 download

1. Bahasa Mengalami Perubahan (Ya, Benar)

1.1. Generic Methods

Sejak Go 1.18 kita telah memiliki generik, dan sejak Go 1.18 pula kita memiliki "Diskusi Tersebut". Isinya seperti ini:

"Izinkan saya menulis metode Map pada tipe slice sayaโ€”"

method must have no type parameters

"...baiklah. Fungsi tingkat paket saja kalau begitu."

Metode sebelumnya hanya dapat menggunakan parameter tipe yang dideklarasikan oleh receiver. Metode Anda sendiri tidak dapat memperkenalkan parameter tipe baru. Jadi, setiap transformasi generik diasingkan ke cakupan paket, di mana ia bersanding dengan tujuh belas pembantu bebas lainnya yang diberi nama variasi dari MapSlice.

Go 1.27 memperbaiki hal ini:

 1type List[E any] []E
 2
 3// F adalah parameter tipe yang dideklarasikan oleh metode itu sendiri
 4func (l List[E]) Apply[F any](f func(E) F) List[F] {
 5	r := make(List[F], len(l))
 6	for i, x := range l {
 7		r[i] = f(x)
 8	}
 9	return r
10}
11
12func main() {
13	l := List[int]{1, 2, 3}
14	fmt.Println(l.Apply(func(i int) string { return fmt.Sprint(i * 10) }))
15	// [10 20 30]
16}

Receiver bahkan tidak perlu bersifat generik. Struct biasa pun dapat memiliki metode generik:

1type Bag struct{ items []any }
2
3func (b *Bag) Add[T any](v T) { b.items = append(b.items, v) }

Sekarang, sebelum Anda melakukan refaktorisasi seluruh basis kode Anda sore ini, terdapat dua batasan, dan batasan ini lebih penting daripada kelihatannya:

  1. Metode antarmuka (interface) tidak dapat mendeklarasikan parameter tipe.
  2. Metode generik tidak dapat mengimplementasikan metode antarmuka.

Batasan kedua itulah yang akan menyulitkan Anda:

1type Adder interface{ Add(int) int }
2
3type G struct{}
4
5func (G) Add[T any](v T) T { return v }
6
7var _ Adder = G{}
1cannot use G{} (value of struct type G) as Adder value in variable declaration:
2	G does not implement Adder (wrong type for method Add)
3		have Add[T any](T) T
4		want Add(int) int

Jadi, metode generik dan dynamic dispatch tidak bisa digabungkan. Ini bukan karena tim Go pelit โ€” melainkan karena metode generik memiliki instansiasi yang tak terhingga, sementara tabel metode antarmuka harus bersifat terbatas dan diketahui saat waktu kompilasi. Anda tidak bisa memasukkan hal yang tak terhingga ke dalam tabel yang terbatas. Alam semesta mengatakan tidak.

Inti praktisnya: metode generik ditujukan untuk tipe konkret dengan API berbentuk utilitas. Segala sesuatu yang harus disembunyikan di balik antarmuka masih memerlukan pendekatan lama.

Pustaka standar sudah memanfaatkannya. Rand pada math/rand/v2 kini memiliki metode generik:

1func (r *Rand) N[Int intType](n Int) Int
1r := rand.New(rand.NewPCG(1, 2))
2fmt.Println(r.N(int32(100)))     // 76
3fmt.Println(r.N(time.Second))    // 616.436223ms

Sebelumnya hanya rand.N tingkat paket yang bersifat generik, yang berarti harus menggunakan sumber global. Sekarang *Rand Anda sendiri yang memiliki seed mendapatkan kenyamanan yang sama. Hal kecil. Hal yang bagus.

1.2. Struct Literal Field Selectors, atau: Masalah #9859 Akhirnya Selesai

Embedding memberi Anda u.ID. Embedding tidak memberi Anda User{ID: 1}. Sebaliknya, ia memberi Anda User{Base: Base{ID: 1}}, yang merupakan cara Go untuk menanyakan apakah Anda benar-benar bermaksud demikian.

Masalah #9859 diajukan pada tahun 2015. Sekarang masalah tersebut telah ditutup. Di suatu tempat, seorang gopher yang telah berganti karier dua kali mungkin baru saja menerima notifikasi GitHub.

 1type Object struct{ name, color string }
 2type Point3D struct {
 3	Object
 4	x, y, z float64
 5}
 6type Line struct {
 7	Object
 8	p, q Point3D
 9}
10
11// Go 1.27: gunakan field yang dipromosikan secara langsung
12line := Line{name: "diagonal", q: Point3D{y: -4, z: 12.3}}

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

Sekarang untuk detail teknisnya, yang saya periksa dengan benar-benar mencoba mengompilasi semuanya.

(a) Kuncinya tetap berupa pengenal (identifier) biasa. Anda tidak bisa menulis jalur selektor arbitrer.

1_ = Line{Object.name: "x"}   // invalid field name Object.name in struct literal
2_ = Line{p.x: 1}             // invalid field name p.x in struct literal

Jadi fiturnya bukan "kunci sekarang bekerja seperti akses field". Melainkan secara spesifik "nama field yang dipromosikan secara implisit diperbolehkan". Sedikit lebih sempit daripada yang disarankan judulnya.

(b) Anda tidak dapat menentukan field yang di-embed dan sesuatu yang dipromosikan darinya secara bersamaan.

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

Masuk akal. Anda memberikan dua instruksi yang bertentangan tentang memori yang sama dan sistem menolak untuk menebak.

(c) Pointer embedding tidak diundang.

1type PtrEmbed struct {
2	*Object
3	z int
4}
5
6_ = PtrEmbed{name: "x"}
7// invalid implicit pointer indirection to reach name

Untuk mengikuti pointer, Anda memerlukan pointer yang bisa diikuti, dan pada saat konstruksi literal, pointer tersebut belum ada. Ini juga adil.

Kabar baik: go fix akan menulis ulang literal lama Anda untuk Anda. Lebih lanjut mengenai hal ini nanti.

1.3. Inferensi Tipe Fungsi Menjadi Tidak Terlalu Arbitrer

Inferensi tipe untuk fungsi generik dulu bekerja di beberapa tempat dan tidak di tempat lain, tanpa prinsip yang jelas mengapa demikian. Menetapkan ke variabel? Baik. Menempatkan fungsi yang sama persis di field struct? Kesalahan kompilator, harap tulis double[int] seolah-olah sekarang tahun 2022.

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

 1func double[T ~int | ~float64](v T) T { return v * 2 }
 2
 3type S struct{ f func(int) int }
 4type A [1]func(int) int
 5
 6func main() {
 7	s := S{f: double}          // 1.26: memerlukan double[int]
 8	a := A{double}             // 1.26: memerlukan double[int]
 9
10	c := make(chan func(int) int, 1)
11	c <- double                // 1.26: memerlukan double[int]
12
13	var fn func(float64) float64 = double  // ini selalu berhasil
14
15	fmt.Println(s.f(21), a[0](5), (<-c)(7), fn(1.5))
16	// 42 10 14 3
17}

Satu fungsi double, diinstansiasi sebagai func(int) int di tiga tempat dan func(float64) float64 di tempat keempat, semuanya dari konteks. Jika Anda menulis struct opsi atau tabel handler yang penuh dengan nilai fungsi, tumpukan kebisingan [T] akan segera hilang dari basis kode Anda.

2. Runtime: Performa Gratis dan Detektor Kebocoran

2.1. Alokasi Khusus Ukuran (Size-Specialized Allocation)

Kompilator sekarang mengeluarkan panggilan ke rutinitas alokasi khusus ukuran untuk objek kecil. Catatan rilis mengklaim efisiensi hingga 30% untuk alokasi di bawah 80 byte, dan sekitar 1% secara keseluruhan untuk program yang intens melakukan alokasi.

Saya tidak percaya catatan rilis tanpa melakukan pengukuran, jadi:

 1type small struct{ a, b, c, d int }
 2
 3var sink any
 4
 5func BenchmarkAlloc(b *testing.B) {
 6	for b.Loop() {
 7		sink = &small{}
 8	}
 9}
10
11func BenchmarkSlice(b *testing.B) {
12	for b.Loop() {
13		sink = make([]byte, 48)
14	}
15}
1# default (size-specialized malloc aktif)
2BenchmarkAlloc-12     3000000     5.358 ns/op
3BenchmarkSlice-12     3000000    13.58 ns/op
4
5# GOEXPERIMENT=nosizespecializedmalloc
6BenchmarkAlloc-12     3000000     8.722 ns/op
7BenchmarkSlice-12     3000000    20.81 ns/op
8

30โ€“35% lebih cepat dalam microbenchmark yang hanya melakukan alokasi pada Apple Silicon. Dengan kata lain: ini adalah skenario terbaik, yang dicapai dalam kondisi laboratorium oleh benchmark yang dirancang agar angka terlihat bagus. Dalam program nyata, GC dan pekerjaan aktual akan mengompensasi sebagian besar dari itu. Klaim ~1% adalah yang jujur.

Biayanya adalah ukuran biner. Hello World:

12413202 bytes  (default)
22361826 bytes  (nosizespecializedmalloc)

Sekitar 50KB, tetap, terlepas dari program Anda. Anda dapat memilih untuk tidak menggunakannya dengan GOEXPERIMENT=nosizespecializedmalloc, tetapi pintu keluar darurat tersebut dijadwalkan untuk dihapus di Go 1.28, jadi anggap itu sebagai solusi untuk laporan bug, bukan gaya hidup.

2.2. Profil Kebocoran Goroutine Kini Benar-Benar Ada

Eksperimental di Go 1.26, tersedia secara umum di 1.27. GOEXPERIMENT goroutineleakprofile telah hilang; fitur ini langsung berfungsi.

 1func leak() {
 2	ch := make(chan int) // tidak akan ada yang mengirim. tidak ada.
 3	go func() {
 4		<-ch
 5	}()
 6}
 7
 8func main() {
 9	for range 3 {
10		leak()
11	}
12	runtime.GC()
13	runtime.GC()
14	pprof.Lookup("goroutineleak").WriteTo(os.Stdout, 1)
15}
1goroutineleak profile: total 3
23 @ 0x104de3f18 0x104d7f0b0 0x104d7ec34 0x104e374f4 0x104dea024
3#	0x104e374f3	main.leak.func1+0x23	/tmp/go127/leak/main.go:12

Tiga goroutine, macet secara permanen, dengan nomor file dan baris yang menunjuk tepat di mana Anda melakukannya.

Trik di balik ini benar-benar cerdas: ia menggunakan kembali analisis keterjangkauan garbage collector. Jika goroutine G diblokir pada primitif P, dan P tidak dapat dijangkau dari goroutine mana pun yang dapat dijalankan (atau apa pun yang bisa dibangunkan oleh goroutine tersebut), maka tidak ada yang bisa menyentuh P lagi, jadi G tidak akan pernah bangun. Itu bukan heuristik โ€” itu adalah bukti.

Desain yang sama memberikan batasan secara gratis: jika channel atau mutex dapat dijangkau melalui variabel global, atau melalui variabel lokal dari goroutine yang masih berjalan, GC masih bisa melihatnya, sehingga runtime tidak bisa menyimpulkan apa pun. Perhatikan bahwa contoh mainan di atas pun memerlukan dua panggilan runtime.GC() untuk melaporkan. Fitur ini tidak akan menangkap segalanya.

Namun, fitur ini akan menangkap kasus klasik "lupa membatalkan konteks, worker goroutine hidup selamanya" โ€” yang merupakan sebagian besar kasus kebocoran.

Jika Anda mengimpor net/http/pprof, fitur ini juga tersedia di /debug/pprof/goroutineleak. Hubungkan ke lingkungan staging, periksa setiap minggu, dan bersiaplah terkejut.

Pekerjaan ini disumbangkan oleh Vlad Saioc di Uber, yang layak mendapatkan minuman pilihannya.

2.3. Traceback Kini Memberi Tahu Permintaan Mana yang Mati

Untuk modul yang mendeklarasikan Go 1.27 atau setelahnya, baris header traceback kini menyertakan label goroutine runtime/pprof.

 1func handle(ctx context.Context) {
 2	var wg sync.WaitGroup
 3	wg.Go(func() {
 4		buf := make([]byte, 4<<10)
 5		os.Stdout.Write(buf[:runtime.Stack(buf, true)])
 6	})
 7	wg.Wait()
 8}
 9
10func main() {
11	labels := pprof.Labels("request", "GET /orders/42", "tenant", "acme")
12	pprof.Do(context.Background(), labels, handle)
13}
1goroutine 3 [running] {request: "GET /orders/42", tenant: acme}:
2main.handle.func1()
3	/tmp/go127/tblabel/main.go:15 +0x40
4sync.(*WaitGroup).Go.func1()
5	...
6goroutine 1 [sync.WaitGroup.Wait] {request: "GET /orders/42", tenant: acme}:
7...

Detail pentingnya: label diwariskan oleh goroutine anak. Goroutine 3 tidak pernah menetapkan label. Ia mendapatkannya dari induknya.

Jadi, lain kali saat produksi mengalami deadlock dan seseorang melakukan SIGQUIT pada proses, alih-alih 4.000 goroutine yang semuanya terlihat identik, Anda mendapatkan 4.000 goroutine yang ditandai dengan permintaan dan tenant mana mereka berasal. Tiga baris pprof.Do pada titik entri permintaan Anda memberikan itu semua.

Karena label dapat berisi hal-hal yang mungkin tidak ingin Anda buang ke stderr, GODEBUG=tracebacklabels=0 akan mematikannya, dan opsi pengecualian tersebut secara eksplisit dimaksudkan untuk tetap ada selamanya.

2.4. asynctimerchan Telah Hilang Selamanya

Go 1.23 membuat channel timer tidak di-buffer (sinkron) dan menawarkan asynctimerchan=1 sebagai cara kembali ke perilaku lama. Di 1.27, pengaturan tersebut dihapus secara permanen. Channel paket time bersifat sinkron, titik, tidak ada negosiasi.

Bagian yang menarik adalah kebijakan yang diperkenalkan bersamanya. GODEBUG yang dihapus namun tetap ada di go.mod Anda tidak secara otomatis merusak build โ€” itu hanya akan rusak jika diatur ke nilai lama:

1# go.mod berisi: godebug asynctimerchan=1
2go: error loading go.mod:
3go.mod:5: removed GODEBUG "asynctimerchan" set to old value "1" (https://go.dev/doc/godebug#go-127)
4
5# go.mod berisi: godebug asynctimerchan=0
6(build berhasil)

Yang berarti satu-satunya orang yang ditegur adalah mereka yang benar-benar mengandalkan perilaku yang sudah dihapus. Semua orang yang menetapkannya ke nilai default akhir dan melupakannya tetap bisa tidak memikirkannya. Itu adalah bagian arkeologi API yang bijaksana.

3. Pustaka Standar

3.1. encoding/json/v2: Mereka Mengganti Mesin di Tengah Penerbangan

Ini adalah perubahan besar. Diskusi proposal selama dua tahun lebih akhirnya membuahkan hasil.

Kini ada tiga paket, dan memahami pembagiannya adalah kunci utamanya:

PaketTugas
encoding/jsonAPI v1 yang Anda kenal. Perilaku 100% tidak berubah. Kini diimplementasikan di atas v2
encoding/json/v2Pemrosesan semantik. Nilai Go โ†” JSON
encoding/json/jsontextPemrosesan sintaksis. JSON sebagai aliran token

Beritanya adalah bahwa encoding/json dibangun kembali dengan implementasi yang benar-benar berbeda namun berperilaku identik. Berikut caranya:

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

Setiap keunikan v1 warisan dienkode sebagai opsi, dibundel ke dalam DefaultOptionsV1(), dan API v1 selalu menerapkan bundel tersebut. Yang berarti ini berperilaku sama di 1.26 dan 1.27:

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

Ya, v1 masih diam-diam menerima kunci duplikat dan mengambil yang terakhir. Dulu selalu begitu. Masih begitu. Kompatibilitas berarti kompatibilitas dengan bagian-bagian buruknya juga.

Sementara itu, v2 memiliki opini:

1var m map[string]int
2jsonv2.Unmarshal([]byte(`{"a":1,"a":2}`), &m)
3// jsontext: duplicate object member name "a"
4
5var s string
6jsonv2.Unmarshal([]byte("\"\xff\""), &s)
7// jsontext: invalid UTF-8 after offset 1

Kunci duplikat dan UTF-8 yang tidak valid ditolak. Keduanya benar-benar berbahaya, bukan sekadar tidak rapi โ€” ketika dua parser tidak setuju tentang kunci duplikat mana yang menang, Anda mendapatkan celah keamanan. CVE-2017-12635 pada CouchDB adalah persis seperti ini: badan JSON dengan dua kunci roles, di mana validator membaca satu dan lapisan penyimpanan membaca yang lain. Menolak adalah keputusan yang tepat.

Opsi bersifat variadik:

 1type Config struct {
 2	Name    string   `json:"name"`
 3	Tags    []string `json:"tags,omitzero"`
 4	Timeout int      `json:"timeout,omitzero"`
 5}
 6
 7out, _ := jsonv2.Marshal(Config{Name: "api"})
 8// {"name":"api"}
 9
10// kunci map yang diurutkan + indentasi
11out, _ = jsonv2.Marshal(map[string]int{"b": 2, "a": 1},
12	jsonv2.Deterministic(true), jsontext.WithIndent("  "))
13// {
14//   "a": 1,
15//   "b": 2
16// }
17
18// tolak field yang tidak dikenal โ€” sebelumnya memerlukan Decoder dan DisallowUnknownFields()
19var c Config
20err := jsonv2.Unmarshal([]byte(`{"name":"api","nope":1}`), &c,
21	jsonv2.RejectUnknownMembers(true))
22// json: cannot unmarshal JSON string into Go main.Config:
23//   unknown object member name "nope"

Deterministic, MatchCaseInsensitiveNames, StringifyNumbers, FormatNilSliceAsNull, OmitZeroStructFields โ€” sebagian besar hal yang sebelumnya Anda selesaikan dengan pustaka pihak ketiga atau MarshalJSON tulisan tangan kini berupa flag.

Dan cerita migrasinya adalah bagian terbaik. Opsi terakhir yang menang, jadi Anda bisa mengadopsi keketatan v2 satu perilaku demi satu perilaku:

1// pertahankan semantik v1, tetapi tolak kunci duplikat seperti v2
2jsonv2.Unmarshal(data, &v,
3	json.DefaultOptionsV1(),
4	jsontext.AllowDuplicateNames(false))
5// duplicate object member name

Anda tidak perlu memindahkan basis kode 200 ribu baris ke v2 hanya untuk berhenti menerima kunci duplikat. Anda cukup mengubah satu opsi. Untuk skala besar, ini adalah jalur yang realistis.

Performa: marshaling kira-kira setara, unmarshaling jauh lebih cepat. Jika ada yang tidak beres, GOEXPERIMENT=nojsonv2 akan mengembalikan implementasi lama โ€” dan opsi pengecualian itu juga pada akhirnya akan dihapus, jadi ajukan masalah alih-alih bertahan dengan yang lama.

jsontext adalah lapisan tingkat rendah: Encoder/Decoder yang menelusuri JSON sebagai Token dan Value dengan mesin status yang menjaga Anda tetap jujur. Gunakan ini saat Anda menulis transformer streaming atau filter JSON dan tidak ingin mematerialisasi nilai Go sama sekali.

3.2. Pustaka uuid Standar

Akhirnya. RFC 9562, dalam pustaka standar, tidak perlu go get.

 1import "uuid"
 2
 3func main() {
 4	fmt.Println(uuid.NewV4())
 5	// b97aa695-da08-472c-af81-ff088129019f
 6
 7	// v7: 48 bit teratas adalah timestamp, jadi ini selalu terurut sesuai urutan pembuatan
 8	a, b := uuid.NewV7(), uuid.NewV7()
 9	fmt.Println(a.Compare(b) < 0) // true
10
11	u, err := uuid.Parse("urn:uuid:0198a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b")
12	fmt.Println(u, err)
13	// 0198a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b <nil>
14
15	fmt.Println(uuid.Nil(), uuid.Max())
16	// 00000000-0000-0000-0000-000000000000 ffffffff-ffff-ffff-ffff-ffffffffffff
17}

Seluruh API-nya adalah New, NewV4, NewV7, Nil, Max, Parse, MustParse, dan satu tipe: type UUID [16]byte. Itu saja. Anda bisa membaca seluruh dokumentasi pustaka ini sambil menunggu kopi Anda mendingin.

Detail yang perlu diketahui:

  • UUID adalah [16]byte, jadi == berfungsi dan dapat langsung digunakan sebagai kunci map. Desain yang sama dengan google/uuid.
  • Nil dan Max adalah fungsi, bukan variabel. Karena var Nil UUID tingkat paket adalah senjata yang siap menembak kaki Anda sendiri, dan cepat atau lambat seseorang akan menetapkan nilai padanya.
  • Bit acak berasal dari generator yang aman secara kriptografis.
  • Mengimplementasikan encoding.TextMarshaler/TextUnmarshaler/TextAppender, sehingga dapat langsung masuk ke struct JSON.
  • NewV7 adalah yang Anda inginkan untuk kunci primer basis data. Terurut waktu berarti indeks B-tree Anda berhenti mengalami fragmentasi, yang merupakan kelemahan terkenal dari UUID v4.

Anda sekarang bisa menghapus dependensi. Jika Anda memerlukan opsi parsing v1/v3/v5 atau yang lebih canggih, pustaka pihak ketiga masih memiliki peran.

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 bagian lainnya: tanda tangan ML-DSA, yang distandarisasi sebagai FIPS 204.

 1sk, _ := mldsa.GenerateKey(mldsa.MLDSA65())
 2pk := sk.PublicKey()
 3
 4msg := []byte("release the gophers")
 5opts := &mldsa.Options{Context: "gosuda.org/blog"}
 6
 7sig, _ := sk.Sign(rand.Reader, msg, opts)
 8
 9fmt.Println("private key seed:", len(sk.Bytes()), "bytes")  // 32
10fmt.Println("public key:", len(pk.Bytes()), "bytes")        // 1952
11fmt.Println("signature:", len(sig), "bytes")                // 3309
12
13fmt.Println(mldsa.Verify(pk, msg, sig, opts))
14// <nil>
15fmt.Println(mldsa.Verify(pk, msg, sig, &mldsa.Options{Context: "other"}))
16// mldsa: invalid signature

Lihat angka-angka itu. Satu tanda tangan berukuran 3.309 byte. Ed25519 hanya 64 byte. Itu peningkatan 50 kali lipat, dan kunci publiknya hampir 2KB di atasnya. Masukkan beberapa tanda tangan ini ke dalam rantai sertifikat dan handshake TLS Anda mulai memerlukan strategi MTU sendiri.

Ini adalah biaya nyata dari ketahanan kuantum saat ini, dan itulah alasan mengapa tidak ada yang beralih sepenuhnya besok. Tetapi fitur ini sekarang ada di pustaka standar, di tempat yang Anda inginkan sebelum Anda membutuhkannya.

Options.Context adalah pemisahan domain: tanda tangani dengan kunci yang sama untuk tujuan yang berbeda, gunakan konteks yang berbeda untuk masing-masing, dan tanda tangan dari satu konteks tidak akan terverifikasi di konteks lain. Contoh di atas menunjukkan tepat hal tersebut โ€” kunci yang sama, pesan yang sama, konteks berbeda, ditolak.

PrivateKey mengimplementasikan crypto.Signer, sehingga dapat masuk ke antarmuka yang ada. crypto/x509 menangani kunci dan tanda tangan ML-DSA, dan crypto/tls mendukung skema tanda tangan MLDSA44/MLDSA65/MLDSA87 di TLS 1.3.

Ada juga SignDeterministic, yang melewati keacakan โ€” 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 โ€” portabel dan agnostik terhadap lebar vektor. Aktifkan dengan GOEXPERIMENT=simd.

 1// dst = a*x + y
 2func axpy(dst, x, y []float32, a float32) {
 3	va := simd.BroadcastFloat32s(a)
 4	w := va.Len()
 5
 6	i := 0
 7	for ; i+w <= len(x); i += w {
 8		vx := simd.LoadFloat32s(x[i:])
 9		vy := simd.LoadFloat32s(y[i:])
10		vx.MulAdd(va, vy).Store(dst[i:])
11	}
12	// tangani sisa dengan load/store parsial โ€” tidak perlu loop pembersihan skalar
13	if i < len(x) {
14		vx, _ := simd.LoadFloat32sPart(x[i:])
15		vy, _ := simd.LoadFloat32sPart(y[i:])
16		vx.MulAdd(va, vy).StorePart(dst[i:])
17	}
18}
1$ GOEXPERIMENT=simd go1.27rc3 run ./simddemo
2vector bits: 128 emulated: false
3[4 7 10 13 16 19 22 25 28 31]

Pilihan desain yang penting: lebar vektor tidak pernah di-hardcode. Anda bertanya pada va.Len() saat runtime. VectorBitSize() memberi tahu lebar aktual, Emulated() memberi tahu apakah Anda mendapatkan perangkat keras asli atau tiruan perangkat lunak yang sopan. Mac saya melaporkan NEON 128-bit; mesin AVX-512 melaporkan lebih banyak; mesin tanpa dukungan apa pun melaporkan emulasi dan kodenya tetap berjalan.

LoadFloat32sPart/StorePart patut mendapat perhatian khusus. Bagian paling menyebalkan dari SIMD tulisan tangan selalu adalah sisa-sisa di akhir array, dan ini menanganinya tanpa loop skalar terpisah.

Masih eksperimental, API masih tidak stabil, jangan masukkan ke produksi. Namun fakta bahwa ini adalah kode Go dan bukan assembly atau cgo adalah masalah besar yang sesungguhnya.

3.5. hash/maphash.Hasher

Sebuah antarmuka baru yang mendeskripsikan kontrak antara tipe nilai dan kontainer berbasis hash:

1type Hasher[T any] interface {
2	Hash(*Hash, T)
3	Equal(x, y T) bool
4}

Mengapa? Map bawaan Go hanya menerima kunci yang comparable. Anda tidak bisa menggunakan slice sebagai kunci. Anda tidak bisa mendefinisikan "sama dengan mengabaikan huruf besar/kecil." Hasher memecahkan keduanya sekaligus:

 1type CaseInsensitive struct{}
 2
 3func (CaseInsensitive) Hash(h *maphash.Hash, s string) {
 4	h.WriteString(strings.ToLower(s))
 5}
 6func (CaseInsensitive) Equal(x, y string) bool {
 7	return strings.ToLower(x) == strings.ToLower(y)
 8}
 9
10var seed = maphash.MakeSeed()
11
12func hashOf[T any](hr maphash.Hasher[T], v T) uint64 {
13	var h maphash.Hash
14	h.SetSeed(seed)   // seed yang sama, atau ini tidak berarti apa-apa
15	hr.Hash(&h, v)
16	return h.Sum64()
17}
18
19fmt.Println(hashOf(CaseInsensitive{}, "Go") == hashOf(CaseInsensitive{}, "GO"))
20// true

Untuk semantik == biasa, ada ComparableHasher[T]:

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

Tangkapannya: Hasher adalah antarmuka, bukan struktur data. Belum ada tabel hash standar atau Bloom filter yang mengonsumsinya. Ini adalah landasan untuk paket kontainer di masa depan. Hari ini Anda akan menggunakannya saat membangun struktur sendiri, atau mengonsumsi implementasi yang ada seperti go/types.Hasher (yang memungkinkan Anda menggunakan types.Type sebagai kunci map, dengan menghormati Identical).

Manajemen seed adalah tanggung jawab Anda. Jika hashOf di atas membuat seed baru setiap panggilan, nilai yang identik akan menghasilkan hash yang berbeda dan contoh tersebut akan mencetak false โ€” yang persis merupakan bug yang saya tulis pada percobaan pertama saya. Satu seed per kontainer. (Seed diacak untuk mengalahkan serangan DoS hash-flooding, itulah sebabnya itu bukan konstanta.)

3.6. httptest.NewTestServer + synctest.Sleep: Hit yang Mengejutkan

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

testing/synctest lulus di Go 1.25 dan memberi kita jam palsu untuk menguji kode konkuren. Kecuali saat pengujian Anda menyentuh jaringan asli, ilusi tersebut runtuh. httptest.NewTestServer milik Go 1.27 menggunakan jaringan palsu dalam memori, sehingga gelembung tersebut tetap utuh. Dan synctest.Sleep (= time.Sleep + synctest.Wait) melengkapinya.

Berikut adalah pengujian retry-with-exponential-backoff:

 1func TestRetryWithBackoff(t *testing.T) {
 2	synctest.Test(t, func(t *testing.T) {
 3		var hits int
 4		srv := httptest.NewTestServer(t, http.HandlerFunc(
 5			func(w http.ResponseWriter, r *http.Request) {
 6				hits++
 7				if hits < 3 {
 8					w.WriteHeader(http.StatusServiceUnavailable)
 9					return
10				}
11				io.WriteString(w, "ok")
12			}))
13
14		client := srv.Client()
15		start := time.Now()
16		var body string
17		for attempt := range 5 {
18			resp, err := client.Get(srv.URL)
19			if err != nil {
20				t.Fatal(err)
21			}
22			b, _ := io.ReadAll(resp.Body)
23			resp.Body.Close()
24			if resp.StatusCode == http.StatusOK {
25				body = string(b)
26				break
27			}
28			synctest.Sleep(time.Duration(1<<attempt) * time.Second)
29		}
30
31		if body != "ok" {
32			t.Fatalf("got %q", body)
33		}
34		// backoff harus tepat 1s + 2s = 3s
35		if elapsed := time.Since(start); elapsed != 3*time.Second {
36			t.Fatalf("elapsed = %v, want 3s", elapsed)
37		}
38		t.Logf("hits=%d elapsed=%v", hits, time.Since(start))
39	})
40}
1=== RUN   TestRetryWithBackoff
2    x_test.go:48: hits=3 elapsed=3s
3--- PASS: TestRetryWithBackoff (0.00s)

Baca itu dua kali. Itu mensimulasikan backoff selama tiga detik terhadap server HTTP asli, dan selesai dalam 0,00 detik. Dan asersinya adalah elapsed == 3*time.Second โ€” tepat tiga detik, bukan "setidaknya tiga detik, kurang lebih jitter penjadwal". Jam palsu tidak mengalami jitter.

synctest.Sleep ada karena alasan khusus: jika pengujian Anda tidur selama durasi yang sama dengan kode yang diuji, siapa yang bangun lebih dulu adalah tebakan siapa pun. synctest.Sleep tidur dan kemudian menunggu sampai setiap goroutine lain dalam gelembung terblokir secara permanen, sehingga Anda mengamati sistem setelah sistem tenang.

Timeout, retry, circuit breaker, rate limiter โ€” setiap pengujian klien HTTP yang bergantung pada waktu yang Anda miliki bisa menjadi cepat dan deterministik. Jika rangkaian pengujian Anda saat ini disatukan dengan time.Sleep(100 * time.Millisecond) dan harapan, ini adalah jalan keluar Anda.

3.7. Perubahan net/http yang Benar-Benar Memengaruhi Produksi

Senyap, tetapi akan muncul di metrik Anda.

Badan respons HTTP/1 secara otomatis dikuras saat Close. Konten yang belum dibaca sekarang dikuras (hingga batas konservatif) saat Anda menutup badan, sehingga koneksi dapat digunakan kembali. Yang berarti Anda akhirnya bisa menghapus mantera yang disalin-tempel semua orang dari jawaban Stack Overflow yang sama sejak 2016:

1// tidak lagi diperlukan
2defer func() {
3	io.Copy(io.Discard, resp.Body)
4	resp.Body.Close()
5}()

Untuk sebagian besar program, ini adalah no-op atau kemenangan kecil. Jika ini membuat keadaan lebih buruk, Anda mungkin berada dalam kategori yang dijelaskan catatan rilis dengan sopan: Transport.MaxIdleConns diatur ke 0, atau Client baru per permintaan, yang sepenuhnya melewati batas koneksi idle. Transport.DisableKeepAlives = true akan menutupinya, tetapi saran sebenarnya dari catatan rilis adalah bahwa "melihat lebih dalam mungkin bermanfaat," yang merupakan cara tim Go mengatakan Anda memiliki masalah yang lebih besar.

Prioritas klien HTTP/2 (RFC 9218). Server sekarang menghormati sinyal prioritas klien. Jika Anda lebih suka penjadwalan round-robin lama, gunakan Server.DisableClientPriority = true.

Server.MaxHeaderValueCount. Membatasi berapa banyak nilai header yang akan diterima server, defaultnya adalah DefaultMaxHeaderValueCount. Satu pintu lagi ditutup untuk serangan "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 di atasnya โ€” sehingga dialer kustom yang melewati proxy masih dapat menegosiasikan HTTP/2.

3.8. Hal-Hal Kecil yang Membawa Kegembiraan

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

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

Sangat bagus untuk ekstensi file dan untuk parsing 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 eksplisit, menggantikan lempar koin "apakah Quo atau Div yang saya inginkan?":

1x, y := big.NewInt(-7), big.NewInt(2)
2
3new(big.Int).Divide(x, y, new(big.Int), big.Trunc)  // q=-3 r=-1
4new(big.Int).Divide(x, y, new(big.Int), big.Floor)  // q=-4 r=1
5new(big.Int).Divide(x, y, new(big.Int), big.Round)  // q=-4 r=1
6new(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 regulasi, ini untuk Anda.

url.URL.Clone dan url.Values.Clone. Deep copy. Shallow copy *u yang lama berbagi pointer Userinfo, yang menghasilkan jenis bug yang butuh waktu sehari untuk ditemukan dan lima detik untuk diperbaiki.

1orig, _ := url.Parse("https://gosuda.org/blog?tag=go&tag=1.27")
2clone := orig.Clone()
3clone.Host = "example.com"
4fmt.Println(orig.Host, clone.Host)  // gosuda.org example.com

Metode baca net.UnixConn sekarang mengembalikan io.EOF secara langsung alih-alih membungkusnya dalam net.OpError. err == io.EOF sekarang berfungsi, seperti yang seharusnya selalu terjadi.

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

unicode 15 โ†’ 17. Dua versi dalam satu lompatan. Perilaku klasifikasi dan normalisasi string mungkin bergeser secara halus. Jika Anda memiliki pengujian yang bergantung padanya, Anda akan segera mengetahuinya.

compress/flate menjadi lebih cepat โ€” dan byte keluarannya mungkin berbeda dari Go 1.26. Ini merambat ke archive/zip, compress/gzip, compress/zlib, dan image/png. Jika Anda memiliki pengujian golden-file yang melakukan hash pada output terkompresi, mereka akan rusak, dan itu bukan regresi. Periksa ini sebelum Anda meningkatkan versi, bukan saat tinjauan insiden.

4. Rantai Alat (Toolchain)

4.1. go fix Menambahkan Lebih Banyak Modernizer

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

 1package fixdemo
 2
 3import (
 4	"sync"
 5	"sync/atomic"
 6)
 7
 8type Base struct{ ID int }
 9type User struct {
10	Base
11	Name string
12}
13
14func New() User {
15	return User{Base: Base{ID: 1}, Name: "gopher"}
16}
17
18var counter int64
19
20func Incr() { atomic.AddInt64(&counter, 1) }
21
22func Reverse(s []string) {
23	for i := len(s) - 1; i >= 0; i-- {
24		_ = s[i]
25	}
26}
27
28func Spawn(wg *sync.WaitGroup) {
29	wg.Add(1)
30	go func() {
31		defer wg.Done()
32	}()
33}
1$ go1.27rc3 fix -diff ./fixdemo
 1 import (
 2+	"slices"
 3 	"sync"
 4 	"sync/atomic"
 5 )
 6
 7 func New() User {
 8-	return User{Base: Base{ID: 1}, Name: "gopher"}
 9+	return User{ID: 1, Name: "gopher"}
10 }
11
12-var counter int64
13+var counter atomic.Int64
14
15-func Incr() { atomic.AddInt64(&counter, 1) }
16+func Incr() { counter.Add(1) }
17
18 func Reverse(s []string) {
19-	for i := len(s) - 1; i >= 0; i-- {
20-		_ = s[i]
21+	for _, v := range slices.Backward(s) {
22+		_ = v
23 	}
24 }
25
26 func Spawn(wg *sync.WaitGroup) {
27-	wg.Add(1)
28-	go func() {
29-		defer wg.Done()
30-	}()
31+	wg.Go(func() {
32+	})
33 }

Empat modernizer berjalan pada satu file kecil:

  • embedlit โ€” menulis ulang literal tertanam 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 memperbaiki bug penyelarasan 32-bit yang tidak Anda sadari
  • slicesbackward โ€” loop mundur menjadi slices.Backward
  • waitgroupgo โ€” ritual Add/go/Done menjadi wg.Go (diganti namanya dari waitgroup di 1.26 untuk menghindari ambiguitas)

go tool fix help mencantumkan ke-26 alat; go tool fix help <name> menjelaskan salah satunya. Selain itu, fmtappendf dihapus "karena masalah gaya," yang merupakan cara diplomatik yang indah untuk menggambarkan apa pun yang terjadi di utas masalah tersebut.

Menjalankan go fix ./... pada basis kode lama adalah sore yang benar-benar memuaskan. Baca diff-nya terlebih dahulu, tentu saja.

4.2. go test Menjalankan stdversion secara Default

Ini adalah perubahan yang paling mungkin mengganggu hari Anda.

go test sekarang menjalankan pemeriksaan vet stdversion secara default, menandai simbol pustaka standar yang lebih baru daripada yang diizinkan oleh direktif go di go.mod Anda:

1// go.mod menyatakan: go 1.24
2package sv
3
4import "strings"
5
6func Ext(name string) string {
7	_, ext, _ := strings.CutLast(name, ".")  // CutLast adalah simbol 1.27
8	return ext
9}
1$ go1.27rc3 test ./...
2# sv
3./x.go:6:23: strings.CutLast requires go1.27 or later (module is go1.24)
4FAIL	sv [build failed]

Ini membunuh mode kegagalan klasik di mana semuanya berfungsi di mesin Anda (rantai alat terbaru) dan meledak di CI atau di lingkungan pengguna yang lebih lama. Jika Anda menerbitkan pustaka dengan direktif go yang konservatif, ini adalah hadiah.

4.3. go test -json Mendapatkan OutputType

Baris "Action":"output" sekarang membawa field opsional "OutputType": "error", "error-continue", atau "frame".

1'frame'  '=== RUN   TestFail\n'
2None     '    x_test.go:6: hello\n'
3'error'  '    x_test.go:7: boom\n'
4'frame'  '--- FAIL: TestFail (0.00s)\n'
5'frame'  'FAIL\n'

Output t.Log (tanpa field), output t.Error (error), dan baris yang dihasilkan framework (frame) sekarang dapat dibedakan. Siapa pun yang pernah menulis regex untuk mencari tahu baris output pengujian mana yang "nyata" sekarang bisa menghapusnya.

4.4. Peningkatan go doc

Sintaks package@version. Baca dokumen untuk versi tertentu tanpa menambahkannya ke modul Anda:

1$ go1.27rc3 doc golang.org/x/sync/errgroup@v0.10.0
2package errgroup // import "golang.org/x/sync/errgroup"
3...

Berguna untuk memeriksa apa yang berubah sebelum peningkatan versi.

-ex dan pencetakan sumber contoh:

 1$ go1.27rc3 doc -ex strings | grep Example
 2    func ExampleClone()
 3    func ExampleCompare()
 4    func ExampleContains()
 5    func ExampleCut()
 6    func ExampleCutPrefix()
 7    ...
 8
 9$ go1.27rc3 doc strings.ExampleCut
10package main
11
12import (
13	"fmt"
14	"strings"
15)
16
17func main() {
18	show := func(s, sep string) {
19		before, after, found := strings.Cut(s, sep)
20		fmt.Printf("Cut(%q, %q) = %q, %q, %v\n", s, sep, before, after, found)
21	}
22	show("Gopher", "Go")
23	...
24}
25
26Output:
27Cut("Gopher", "Go") = "", "pher", true

Contoh sumber dan output yang diharapkan, di terminal, tanpa membuka tab browser yang akan tetap terbuka sampai hari Kamis depan.

4.5. go mod tidy Membersihkan Blok require Anda

Untuk modul pada go 1.27 atau setelahnya, go mod tidy menggabungkan blok require yang tersebar menjadi paling banyak dua (langsung dan tidak langsung), dengan tetap mempertahankan komentar terlampir.

 1// Sebelum
 2module tidydemo
 3
 4go 1.27
 5
 6require golang.org/x/sync v0.10.0
 7
 8// networking
 9require golang.org/x/net v0.33.0
10
11require (
12	golang.org/x/sys v0.28.0 // indirect
13)
14
15require golang.org/x/text v0.21.0 // indirect
 1// Setelah: go mod tidy
 2module tidydemo
 3
 4go 1.27
 5
 6require (
 7	// networking
 8	golang.org/x/net v0.33.0
 9	golang.org/x/sync v0.10.0
10)

Perhatikan bahwa komentar // networking tetap ada. Ini sebagian besar membersihkan setelah resolusi konflik penggabungan Git, yang merupakan tempat blok require liar lahir. Jika tim Anda memiliki lebih dari tiga orang yang menambahkan dependensi, konflik go.mod Anda akan menjadi jauh lebih tenang.

4.6. Hal-hal Lain

  • Dukungan bzr dihapus. Jika ini memengaruhi Anda, saya benar-benar ingin mendengar ceritanya.
  • go tool trace -http mengikat ke localhost ketika hanya diberi port. -http=:6060 tidak lagi mendengarkan di setiap antarmuka. Gunakan -http=0.0.0.0:6060 jika itu yang Anda maksud. Ini sekarang cocok dengan go tool pprof, dan mencegah profiler publik yang tidak disengaja.
  • File respons (@file) didukung oleh compile, link, asm, cgo, cover, dan pack, dalam format yang kompatibel dengan GCC. Untuk sistem build yang melampaui batas panjang baris perintah โ€” halo, Bazel.

5. Kompilator, Linker, Port

Kompilator. Nama file relatif dalam direktif //line sekarang diselesaikan terhadap direktori file yang memuatnya, cocok dengan go/scanner. Relevan jika Anda menulis generator kode.

Nama simbol literal fungsi (closure) juga lebih sederhana sekarang โ€” nama yang sama terlepas dari inlining, dan banyak instansi dari literal yang sama dapat berbagi kode dalam biner. Tidak ada perubahan fungsional, kecuali: kode yang membandingkan identitas fungsi melalui reflect.Value.Pointer akan melihat "sama" lebih sering daripada sebelumnya. Perbandingan itu tidak pernah valid, tetapi jika Anda memilikinya, sekaranglah saatnya ia mulai berbohong kepada Anda dengan lebih keras.

Linker. Opsi -macos dan -macsdk baru mengatur versi OS dan SDK dalam perintah muat LC_BUILD_VERSION macOS.

Port.

  • Darwin sekarang memerlukan macOS 13 Ventura atau setelahnya, seperti yang diumumkan di 1.26. Periksa runner CI Anda.
  • PowerPC (GOOS=linux GOARCH=ppc64) beralih ke ABI ELFv2. Memerlukan kernel Linux 3.13+ (RHEL7 di-backport ke 3.10). Cgo, PIE, dan penautan eksternal sekarang didukung. Jika Anda menggunakan cgo tetapi memerlukan biner Go murni statis, atur CGO_ENABLED=0.

6. Daftar Periksa Peningkatan

Go 1.27 menangani kompatibilitas dengan serius, tetapi periksa hal-hal ini sebelum Anda menaikkan versi:

  • Pengujian golden-file pada output terkompresi. compress/flate mengubah enkoder; byte gzip/zip/png mungkin berbeda.
  • Pengujian yang mencocokkan string pesan kesalahan JSON. Perilakunya identik; teks kesalahannya tidak.
  • GODEBUG yang dihapus di go.mod. asynctimerchan, gotypesalias, tlsrsakex, tls3des, tls10server, tlsunsafeekm, x509keypairleaf โ€” ini menggagalkan build hanya jika diatur ke nilai lama mereka.
  • Pelanggaran stdversion. go test menangkap ini sekarang. Jalankan lebih awal jika Anda memelihara pustaka dengan direktif go yang konservatif.
  • Runner CI macOS 12 atau lebih lama. Dukungan telah dihapus.
  • Pengujian yang menegaskan nama simbol literal fungsi, dan perbandingan fungsi reflect.Value.Pointer.
  • Transport.MaxIdleConns = 0 atau Client per permintaan. Dikombinasikan dengan badan respons yang dikuras otomatis, ini mungkin menjadi lebih lambat.

Pemikiran Penutup

Go 1.27 adalah rilis di mana banyak pemeliharaan yang ditunda harus dibayar sekaligus.

Metode generik adalah bagian yang hilang dari pekerjaan generik 1.18. Selektor field literal struct menutup masalah berumur sebelas tahun. encoding/json/v2 adalah dua tahun diskusi proposal. Dan uuid mengisi lubang yang pada dasarnya setiap proyek Go telah tambal dengan dependensi pihak ketiga yang sama selama satu dekade.

Jika Anda menginginkan urutan prioritas untuk benar-benar mendapatkan nilai dari ini:

  1. httptest.NewTestServer + synctest.Sleep โ€” pengujian HTTP yang bergantung pada waktu menjadi cepat dan deterministik. Rasio usaha-terhadap-hasil terbaik di seluruh rilis.
  2. Profil kebocoran goroutine โ€” buka satu endpoint di staging dan bersiaplah untuk merasa rendah hati.
  3. Label goroutine traceback โ€” tiga baris pprof.Do pada titik entri permintaan Anda mengubah dump stack pukul 3 pagi Anda berikutnya menjadi sesuatu yang dapat dibaca.
  4. go fix ./... โ€” biarkan alat melakukan modernisasi.
  5. encoding/json/v2 โ€” tidak perlu terburu-buru. Adopsi satu opsi demi satu opsi.

Ambil go1.27rc3 dan jalankan rangkaian pengujian Anda sekarang. Semua yang ada di daftar periksa itu jauh lebih menyenangkan untuk ditemukan pada Selasa sore daripada selama insiden.

Referensi