GoSuda

Go 1.27 ya da Endişelenmeyi Bırakmayı ve Jenerik Metotları Sevmeyi Nasıl Öğrendim

By iwanhae views ...

Go 1.27'ye genel bakış

Go 1.27, Ağustos 2026'da yayımlanıyor ve bu kez dil kendisi değişti. "slices paketine yeni bir fonksiyon ekledik" türünden bir değişiklik değil, gerçekten bir değişiklik. Genel metotlar (generic methods) dile girdi. On bir yıllık bir issue kapatıldı. encoding/json, uçak havadayken sessizce yeni bir motorla değiştirildi.

Aşağıdaki her örnek go1.27rc3 (darwin/arm64) üzerinde çalıştırıldı. Her çıktı bloğu gerçek çıktıdır, hata mesajları dahil olmak üzere kopyalanıp yapıştırılmıştır. Bir şey garip görünüyorsa, bunun nedeni derleyicinin gerçekten öyle söylemiş olmasıdır.

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

1. Dil Değişti (Evet, Gerçekten)

1.1. Genel Metotlar

Go 1.18 ile birlikte genericlerimiz var ve Go 1.18 ile birlikte "Konuşma" da var. Şöyle ilerler:

"Bir Map metodunu slice tipime yazayım bari..."

method must have no type parameters

"...tamam. O zaman paket düzeyinde bir fonksiyon olsun."

Metotlar yalnızca alıcı (receiver) tarafından bildirilen tip parametrelerini kullanabiliyordu. Kendi metotlarınız yeni tip parametreleri tanımlayamıyordu. Bu yüzden her generic dönüşüm paket kapsamına sürgün ediliyordu; orada, MapSlice adının bir varyasyonunu taşıyan on yedi başka serbest yardımcı fonksiyonun yanında duruyordu.

Go 1.27 bunu düzeltiyor:

 1type List[E any] []E
 2
 3// F, metodun kendisi tarafından bildirilen bir tip parametresidir
 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}

Alıcının generic olması bile gerekmiyor. Sıradan bir struct da generic bir metoda sahip olabilir:

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

Şimdi, bu öğleden sonra tüm kod tabanınızı yeniden düzenlemeye başlamadan önce, görünüşünden daha önemli olan iki kısıtlama var:

  1. Arayüz metotları tip parametresi bildiremez.
  2. Generic metotlar arayüz metotlarını uygulayamaz.

Sizi ısıracak olan ikincisi:

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

Yani generic metotlar ile dinamik dispatch bir arada kullanılamaz. Bu, Go ekibinin cimri davranması değil: generic bir metodun sonsuz sayıda örneklemesi (instantiation) vardır, oysa bir arayüz metot tablosunun sonlu ve derleme zamanında bilinen olması gerekir. Sonsuz bir şeyi sonlu bir tabloya koyamazsınız. Evren hayır dedi.

Pratik sonuç: generic metotlar, yardımcı nitelikte API'lere sahip somut tipler içindir. Bir arayüzün arkasına saklanması gereken her şey hâlâ eski yaklaşıma ihtiyaç duyar.

Standart kütüphane bundan zaten yararlandı. math/rand/v2 paketindeki Rand bir generic metot kazandı:

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

Daha önce yalnızca paket düzeyindeki rand.N generic'ti, bu da global kaynağı kullanmak anlamına geliyordu. Artık kendi tohumlanmış *Rand değeriniz de aynı kolaylığa sahip. Küçük bir şey. Güzel bir şey.

1.2. Struct Literal Alan Seçicileri ya da: Issue #9859 Nihayet Huzur Buldu

Gömme (embedding), u.ID yazmanıza olanak tanır. Gömme, User{ID: 1} yazmanıza olanak tanımaz. Bunun yerine User{Base: Base{ID: 1}} yazmanızı ister; bu da Go'nun bunu gerçekten kastedip etmediğinizi sormanın yoludur.

Issue #9859 2015 yılında açıldı. Şimdi kapatıldı. Bir yerlerde, o zamandan beri iki kez kariyer değiştirmiş bir gopher, bir GitHub bildirimi alıyordur.

 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: yükseltilmiş alanı doğrudan kullanın
12line := Line{name: "diagonal", q: Point3D{y: -4, z: 12.3}}

name, Line yapısının bir alanı değildir. Gömülü Object yapısının bir alanıdır. Daha önce Line{Object: Object{name: "diagonal"}, ...} yazmanız gerekiyordu.

Şimdi ayrıntılara gelelim; bunların tamamını gerçekten derlemeyi deneyerek kontrol ettim.

(a) Anahtar hâlâ düz bir tanımlayıcıdır. Keyfi bir seçici yolu yazamazsınız.

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

Yani özellik "anahtarlar artık alan erişimi gibi çalışıyor" değildir. Özellik özellikle "dolaylı olarak yükseltilmiş alan adlarına izin verilmesidir." Başlığın ima ettiğinden biraz daha dar.

(b) Gömülü bir alanı ve ondan yükseltilmiş bir alanı aynı anda belirtemezsiniz.

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

Makul. Aynı bellek alanı hakkında birbiriyle çelişen iki şey söylediniz ve derleyici tahmin yürütmeyi reddetti.

(c) İşaretçi gömmesi teşvik edilmiyor.

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

Bir işaretçiyi takip etmek için takip edilecek bir işaretçiye ihtiyacınız var, ve literal oluşturulurken henüz böyle bir işaretçi yoktur. Bu da haklı bir durum.

İyi haber: go fix eski literal'lerinizi sizin için yeniden yazacak. Bunun hakkında daha sonra daha fazla bilgi var.

1.3. Fonksiyon Tip Çıkarımı Daha Az Keyfi Hale Geldi

Generic fonksiyonlar için tip çıkarımı bazı yerlerde çalışır, bazı yerlerde çalışmazdı; hangisinin hangisi olduğunun ardında anlaşılabilir bir ilke yoktu. Bir değişkene atama mı? Sorun yok. Tamamen aynı fonksiyonu bir struct alanına koymak mı? Derleyici hatası, lütfen double[int] yazın, sanki 2022'deymiş gibi.

Go 1.27, hedef tipin açıkça bilindiği her bağlamda çıkarımın çalışmasını sağlıyor:

 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: double[int] yazmak gerekiyordu
 8	a := A{double}             // 1.26: double[int] yazmak gerekiyordu
 9
10	c := make(chan func(int) int, 1)
11	c <- double                // 1.26: double[int] yazmak gerekiyordu
12
13	var fn func(float64) float64 = double  // bu her zaman çalışıyordu
14
15	fmt.Println(s.f(21), a[0](5), (<-c)(7), fn(1.5))
16	// 42 10 14 3
17}

Tek bir double, üç yerde func(int) int olarak, dördüncü yerde func(float64) float64 olarak örneklenir ve bunların tamamı bağlamdan çıkarılır. Fonksiyon değerleriyle dolu seçenek struct'ları veya handler tabloları yazıyorsanız, kod tabanınızdaki bir sürü [T] gürültüsü kaybolmak üzere.

2. Çalışma Zamanı: Bedava Performans ve Bir Sızıntı Dedektörü

2.1. Boyuta Özel Bellek Ayırma

Derleyici artık küçük nesneler için boyuta özel bellek ayırma rutinlerini çağırıyor. Sürüm notları, 80 bayttan küçük ayırmalarda %30'a varan, bellek ayırma yoğun programlarda ise genel olarak yaklaşık %1'lik bir kazanç iddia ediyor.

Ölçmeden sürüm notlarına inanmıyorum, bu yüzden:

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

Apple Silicon üzerinde yalnızca bellek ayıran bir mikro benchmark'ta %30–35 daha hızlı. Yani bu en iyi durum: sayıyı iyi göstermek için tasarlanmış bir benchmark tarafından laboratuvar koşullarında elde edildi. Gerçek bir programda GC ve gerçek iş yükü bunun çoğunu gömecektir. Dikkate alınması gereken dürüst iddia yaklaşık %1'lik olanıdır.

Maliyet ikili dosya boyutudur. Hello World:

12413202 bytes  (default)
22361826 bytes  (nosizespecializedmalloc)

Programınızdan bağımsız olarak sabit yaklaşık 50KB. GOEXPERIMENT=nosizespecializedmalloc ile bu özelliği devre dışı bırakabilirsiniz, ancak bu kaçış yolunun Go 1.28'de kaldırılması planlanıyor, bu yüzden onu bir yaşam tarzı değil, hata raporu için geçici bir çözüm olarak görün.

2.2. Goroutine Sızıntı Profili Artık Gerçek

Go 1.26'da deneyseldi, 1.27'de genel kullanıma sunuldu. goroutineleakprofile GOEXPERIMENT'i kalktı; artık sadece çalışıyor.

 1func leak() {
 2	ch := make(chan int) // kimse asla gönderim yapmayacak. kimse.
 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

Üç goroutine, kalıcı olarak takılmış, tam olarak bunu nerede yaptığınızı gösteren bir dosya ve satır numarasıyla.

Bunun arkasındaki numara gerçekten zekice: çöp toplayıcının erişilebilirlik analizini yeniden kullanıyor. Eğer goroutine G, P ilkelinde bloke olmuşsa ve P, çalıştırılabilir herhangi bir goroutine'den (ya da bu goroutine'lerin uyandırabileceği herhangi bir şeyden) erişilemezse, P'ye hiçbir şey bir daha dokunamaz, dolayısıyla G asla uyanmaz. Bu bir sezgisel değil, bir ispattır.

Aynı tasarım sınırlamayı da bedavaya verir: kanal veya mutex bir global üzerinden veya hâlâ çalışan bir goroutine'in yerel bir değişkeni üzerinden erişilebilirse, GC onu hâlâ görebilir ve çalışma zamanı hiçbir sonuca varamaz. Yukarıdaki basit örneğin bile raporlamak için iki runtime.GC() çağrısına ihtiyaç duyduğuna dikkat edin. Her şeyi yakalamaz.

Yine de klasik "context'i iptal etmeyi unuttum, worker goroutine sonsuza kadar yaşıyor" hatasını yakalar; ki çoğu zaman sızıntıların çoğu budur.

net/http/pprof paketini içe aktarırsanız, bu profil /debug/pprof/goroutineleak adresinde de bulunur. Bunu staging ortamına bağlayın, haftada bir kontrol edin ve sessizce dehşete kapılın.

Bu çalışma Uber'den Vlad Saioc tarafından katkıda bulunuldu; kendilerine seçecekleri bir içecek hak ediyorlar.

2.3. Geri İzlemeler Artık Hangi İsteğin Öldüğünü Söylüyor

Go 1.27 veya üstünü bildiren modüller için, geri izleme (traceback) başlık satırları artık runtime/pprof goroutine etiketlerini içeriyor.

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

Önemli ayrıntı: etiketler alt goroutine'lere devredilir. Goroutine 3 hiç etiket ayarlamadı. Bunu üst goroutine'den aldı.

Yani bir dahaki sefere üretimde bir deadlock yaşandığında ve biri süreci SIGQUIT ile durdurduğunda, hepsi birbirinin aynısı görünen 4.000 goroutine yerine, hangi isteğe ve hangi kiracıya ait olduklarını gösteren etiketlere sahip 4.000 goroutine elde edersiniz. İstek giriş noktanızda üç satırlık pprof.Do bunu size sağlar.

Etiketler stderr'e dökmek istemeyeceğiniz şeyler içerebileceğinden, GODEBUG=tracebacklabels=0 bu özelliği kapatır ve bu devre dışı bırakma seçeneğinin sonsuza dek kalması açıkça amaçlanmıştır.

2.4. asynctimerchan Artık Tamamen Gitti

Go 1.23, zamanlayıcı kanallarını tamponsuz (senkron) hale getirdi ve eski davranışa dönmek için asynctimerchan=1 seçeneğini sundu. 1.27'de bu ayar kalıcı olarak kaldırıldı. time paketinin kanalları senkrondur, kesinlikle, pazarlık yok.

Buna paralel olarak tanıtılan politika ilginç. go.mod dosyanızda kalmış kaldırılmış bir GODEBUG, derlemeyi otomatik olarak bozmaz; yalnızca eski değerine ayarlanmışsa bozar:

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

Bu, azarlanan tek kişilerin, kaldırılan davranışa gerçekten güvenen kişiler olduğu anlamına gelir. Ayarı nihai varsayılan değerine çekip unutanlar, bunu düşünmeye devam etmeme özgürlüğünü korur. Bu, düşünceli bir API arkeolojisi örneği.

3. Standart Kütüphane

3.1. encoding/json/v2: Motor Uçuş Sırasında Değiştirildi

Bu büyük olanı. İki yılı aşkın öneri tartışması nihayet sonuçlandı.

Artık üç paket var ve bu ayrımı anlamak savaşın büyük kısmı:

PaketGörevi
encoding/jsonBildiğiniz v1 API'si. Davranış %100 değişmedi. Artık v2 üzerinde uygulanıyor
encoding/json/v2Anlamsal işleme. Go değerleri ↔ JSON
encoding/json/jsontextSözdizimsel işleme. JSON bir token akışı olarak

Ana nokta, encoding/json'un tamamen farklı bir uygulama üzerine yeniden inşa edilmiş olması ve aynı şekilde davranmasıdır. İşte nasıl:

1// Go 1.27'deki encoding/json.Unmarshal, kısaltılmış
2func Unmarshal(data []byte, v any) error {
3	return jsonv2.Unmarshal(data, v, DefaultOptionsV1())
4}

Her eski v1 tuhaflığı bir seçenek olarak kodlandı, DefaultOptionsV1() içinde paketlendi ve v1 API'si bu paketi her zaman uygular. Bu da aşağıdakinin 1.26 ve 1.27'de aynı davrandığı anlamına gelir:

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

Evet, v1 hâlâ yinelenen anahtarları sessizce kabul eder ve sonuncusunu alır. Her zaman böyleydi. Hâlâ öyle. Uyumluluk, kötü kısımlarla da uyumluluk demektir.

Buna karşılık v2'nin görüşleri var:

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

Yinelenen anahtarlar ve geçersiz UTF-8 reddedilir. Her ikisi de sadece dağınık olmakla kalmayıp gerçekten tehlikelidir; iki ayrıştırıcı hangi yinelenen anahtarın kazandığı konusunda anlaşamadığında güvenlik açıkları ortaya çıkar. CouchDB'nin CVE-2017-12635 açığı tam olarak buydu: iki roles anahtarı içeren bir JSON gövdesinde, doğrulayıcı birini okudu, depolama katmanı diğerini okudu. Reddetmek doğru karar.

Seçenekler değişkendir (variadic):

 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// sıralanmış map anahtarları + girinti
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// bilinmeyen alanları reddet — daha önce bir Decoder ve DisallowUnknownFields() gerekiyordu
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: daha önce üçüncü taraf bir kütüphaneyle ya da elle yazılmış bir MarshalJSON ile çözdüğünüz şeylerin çoğu artık bayraklar.

Ve geçiş hikâyesi en iyi kısmı. Sonraki seçenekler kazanır, bu yüzden v2'nin katılığını bir seferde bir davranış olarak benimseyebilirsiniz:

1// v1 semantiğini koru, ama v2'nin yaptığı gibi yinelenen anahtarları reddet
2jsonv2.Unmarshal(data, &v,
3	json.DefaultOptionsV1(),
4	jsontext.AllowDuplicateNames(false))
5// duplicate object member name

Yinelenen anahtarları kabul etmeyi bırakmak için 200 bin satırlık bir kod tabanını v2'ye taşımanız gerekmez. Tek bir seçeneği değiştirirsiniz. Büyük projelerde bu gerçekçi yoldur.

Performans: marshal işlemi hemen hemen eşit, unmarshal işlemi ise önemli ölçüde daha hızlı. Bir sorun olursa, GOEXPERIMENT=nojsonv2 eski uygulamayı geri getirir; ama bu kaçış yolu da sonunda kaldırılacak, bu yüzden buna alışmak yerine bir issue açın.

jsontext düşük seviyeli katmandır: JSON'u Token ve Value olarak gezen, sizi dürüst tutan bir durum makinesiyle çalışan Encoder/Decoder bileşenleridir. Akışkan bir dönüştürücü ya da JSON filtresi yazıyorsanız ve Go değerlerini hiç somutlaştırmak istemiyorsanız buna başvurun.

3.2. Standart uuid Paketi

Sonunda. RFC 9562, standart kütüphanede, go get gerektirmeden.

 1import "uuid"
 2
 3func main() {
 4	fmt.Println(uuid.NewV4())
 5	// b97aa695-da08-472c-af81-ff088129019f
 6
 7	// v7: ilk 48 bit bir zaman damgasıdır, bu yüzden bunlar her zaman oluşturulma sırasına göre sıralanır
 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}

Tüm API şunlardan oluşuyor: New, NewV4, NewV7, Nil, Max, Parse, MustParse ve tek bir tip: type UUID [16]byte. Bu kadar. Kahveniz soğurken tüm paket belgelerini okuyabilirsiniz.

Bilmeye değer ayrıntılar:

  • UUID, [16]byte tipindedir, bu yüzden == çalışır ve doğrudan bir map anahtarı olarak kullanılabilir. google/uuid ile aynı tasarım.
  • Nil ve Max değişken değil, fonksiyondur. Çünkü paket düzeyinde bir var Nil UUID ayağınıza doğrultulmuş yüklü bir silahtır ve birileri bir gün ona atama yapacaktır.
  • Rastgele bitler kriptografik olarak güvenli bir üreteçten gelir.
  • encoding.TextMarshaler/TextUnmarshaler/TextAppender arayüzlerini uygular, bu yüzden JSON struct'larına doğrudan oturur.
  • Veritabanı birincil anahtarları için istediğiniz NewV7'dir. Zamana göre sıralı olması, B-ağacı indeksinizin parçalanmasını durdurur; v4 UUID'lerin bu konuda bilinen şekilde kötü olduğu bir durumdur.

Artık bir bağımlılığı silebilirsiniz. v1/v3/v5 ya da daha gelişmiş ayrıştırma seçeneklerine ihtiyacınız varsa, üçüncü taraf kütüphanelerin hâlâ bir işi var.

3.3. crypto/mldsa: Kuantum Sonrası İmzalar ve Bunlar Çok Büyük

Go 1.24, kuantum sonrası anahtar değişimi için crypto/mlkem paketini verdi. Go 1.27 diğer yarısını getiriyor: FIPS 204 olarak standartlaştırılmış ML-DSA imzaları.

 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

Bu sayılara bakın. Tek bir imza 3.309 bayttır. Ed25519 ise 64 bayttır. Bu 50 kat bir artış demek ve açık anahtar bunun üzerine neredeyse 2KB ekliyor. Bunlardan birkaçını bir sertifika zincirine yerleştirirseniz, TLS el sıkışmanız kendi MTU stratejisine ihtiyaç duymaya başlar.

Bu, bugün kuantum direncinin gerçek maliyeti ve herkesin yarın her şeyi değiştirmemesinin nedeni. Ama artık standart kütüphanede, ve ihtiyaç duymadan önce orada olması gereken yer burası.

Options.Context alan ayrımıdır: aynı anahtarla farklı amaçlar için imzalayın, her biri için farklı bir context kullanın ve bir context'teki imza diğerinde doğrulanmaz. Yukarıdaki örnek tam olarak bunu gösteriyor: aynı anahtar, aynı mesaj, farklı context, reddedildi.

PrivateKey, crypto.Signer arayüzünü uygular, bu yüzden mevcut arayüzlere uyar. crypto/x509 ML-DSA anahtarlarını ve imzalarını işler, crypto/tls ise TLS 1.3'te MLDSA44/MLDSA65/MLDSA87 imza şemalarını destekler.

Ayrıca rastgeleliği atlayan SignDeterministic de var; testler ve yeniden üretilebilir derlemeler için kullanışlı.

3.4. simd: Assembly Olmadan Vektör Komutları

Go 1.26, mimariye özgü simd/archsimd paketini deney olarak tanıttı. Go 1.27 simd paketini ekliyor: taşınabilir ve vektör genişliğinden bağımsız. GOEXPERIMENT=simd ile etkinleştirin.

 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	// kuyruğu kısmi yükleme/saklama ile işle — skaler temizleme döngüsü yok
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]

Önemli olan tasarım seçimi: vektör genişliği asla sabit kodlanmaz. Çalışma zamanında va.Len() sorarsınız. VectorBitSize() gerçek genişliği söyler, Emulated() ise gerçek donanım mı yoksa kibar bir yazılım taklidi mi aldığınızı söyler. Benim Mac'im 128 bitlik NEON bildirdi; bir AVX-512 makinesi daha fazla bildirir; hiçbir şeyi olmayan bir makine ise emülasyon bildirir ve kod yine de çalışır.

LoadFloat32sPart/StorePart özel bir değinmeyi hak ediyor. El yazımı SIMD'nin en can sıkıcı kısmı her zaman dizinin sonundaki düzensiz kuyruktur ve bu, ayrı bir skaler döngüye ihtiyaç duymadan bunu çözer.

Hâlâ deneysel, API hâlâ kararsız, üretime koymayın. Ama bunun assembly ya da cgo değil de Go kodu olması gerçekten büyük bir olay.

3.5. hash/maphash.Hasher

Bir değer tipi ile hash tabanlı konteynerler arasındaki sözleşmeyi tanımlayan yeni bir arayüz:

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

Neden? Go'nun yerleşik map'i yalnızca comparable anahtarları kabul eder. Bir slice üzerine anahtar koyamazsınız. "Büyük/küçük harf duyarsız eşitlik" tanımlayamazsınız. Hasher her ikisini birden çözer:

 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)   // aynı tohum, yoksa bunların hiçbirinin bir anlamı yok
15	hr.Hash(&h, v)
16	return h.Sum64()
17}
18
19fmt.Println(hashOf(CaseInsensitive{}, "Go") == hashOf(CaseInsensitive{}, "GO"))
20// true

Sıradan == semantiği için ComparableHasher[T] vardır:

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

Tuzak şu: Hasher bir arayüzdür, bir veri yapısı değildir. Onu tüketen standart bir hash tablosu ya da Bloom filtresi henüz yok. Bu, gelecekteki bir konteyner paketi için zemin hazırlığı. Bugün, kendi yapınızı oluştururken ya da types.Type değerini bir map anahtarı olarak kullanmanıza (ve Identical ilişkisine uymanıza) izin veren go/types.Hasher gibi mevcut bir uygulamayı tüketirken kullanırsınız.

Tohum yönetimi size kalmış. Yukarıdaki hashOf her çağrıda yeni bir tohum üretseydi, aynı değerler farklı hashlenirdi ve örnek false yazdırırdı; ki ilk denemem de tam olarak bu hataydı. Konteyner başına bir tohum. (Tohum, hash-flooding DoS saldırılarını engellemek için rastgele seçilir; bu yüzden sabit bir değer değildir.)

3.6. httptest.NewTestServer + synctest.Sleep: Uyuyan Adam Vurdu

Bu sürümden yalnızca bir şey benimseyecekseniz, bu olsun.

testing/synctest, Go 1.25'te genel kullanıma geçti ve eşzamanlı kod testleri için sahte bir saat sağladı. Ancak testiniz gerçek bir ağa dokunduğu an illüzyon çöküyordu. Go 1.27'nin httptest.NewTestServer fonksiyonu bellek içi sahte bir ağ kullanır, bu yüzden balon bozulmadan kalır. Ve synctest.Sleep (= time.Sleep + synctest.Wait) tabloyu tamamlar.

İşte üstel geri çekilmeli (exponential backoff) yeniden deneme testi:

 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		// geri çekilme tam olarak 1s + 2s = 3s olmalı
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)

Bunu iki kez okuyun. Gerçek bir HTTP sunucusuna karşı üç saniyelik geri çekilmeyi simüle etti ve 0.00 saniyede bitirdi. Ve doğrulama elapsed == 3*time.Second şeklinde: tam olarak üç saniye, "en az üç saniye, zamanlayıcı gecikmesi dahil" değil. Sahte saatler titremez.

synctest.Sleep belirli bir nedenle var: testiniz, test edilen kodla aynı süre kadar uyursa, hangisinin önce uyanacağı tahmin yürütmeye kalır. synctest.Sleep uyur ve sonra balondaki diğer tüm goroutine'ler kalıcı olarak bloke olana kadar bekler, böylece sistemi yerleştikten sonra gözlemlersiniz.

Zaman aşımları, yeniden denemeler, devre kesiciler, hız sınırlayıcılar: zamana bağlı her HTTP istemci testi hızlı ve deterministik hale gelebilir. Test paketiniz şu anda time.Sleep(100 * time.Millisecond) ve umutla bir arada tutuluyorsa, bu sizin çıkışınızdır.

3.7. Üretimi Gerçekten Etkileyen net/http Değişiklikleri

Sessiz, ama metriklerinizde görünecekler.

HTTP/1 yanıt gövdeleri Close üzerinde otomatik olarak boşaltılır. Okunmamış içerik, gövdeyi kapattığınızda (muhafazakâr bir sınıra kadar) boşaltılır, böylece bağlantı yeniden kullanılabilir. Bu da 2016'dan beri herkesin aynı Stack Overflow cevabından kopyalayıp yapıştırdığı bu büyüyü artık silebileceğiniz anlamına gelir:

1// artık gerekli değil
2defer func() {
3	io.Copy(io.Discard, resp.Body)
4	resp.Body.Close()
5}()

Çoğu program için bu ya bir işlem yapmaz ya da küçük bir kazançtır. Eğer işleri kötüleştiriyorsa, muhtemelen sürüm notlarının nazikçe tarif ettiği kategoridesiniz: Transport.MaxIdleConns 0 olarak ayarlanmış ya da her istek için yeni bir Client, bu da boşta bağlantı sınırını tamamen atlatıyor. Transport.DisableKeepAlives = true bunu örtbas eder, ama sürüm notlarının asıl tavsiyesi "daha derin bir inceleme muhtemelen faydalı olacaktır" şeklinde, ki bu Go ekibinin dilinde daha büyük sorunlarınız var demektir.

HTTP/2 istemci önceliği (RFC 9218). Sunucu artık istemcinin öncelik sinyallerine uyar. Eski round-robin zamanlamayı tercih ettiyseniz, Server.DisableClientPriority = true.

Server.MaxHeaderValueCount. Sunucunun kabul edeceği başlık değeri sayısını sınırlar, varsayılanı DefaultMaxHeaderValueCount'tır. "On bin başlık gönder ve ne olacağını izle" yönteminin kapısına bir kapı daha kapatıldı.

Kullanıcı tarafından sağlanan bağlantılarda ALPN. net.Conn değeriniz ConnectionState() tls.ConnectionState metodunu uyguluyorsa, Transport ve Server bu bağlantı üzerinde TLS ALPN müzakeresi yapar; böylece bir proxy üzerinden geçen özel dialer'lar yine de HTTP/2'yi müzakere edebilir.

3.8. Keyif Veren Küçük Şeyler

strings.CutLast / bytes.CutLast. Cut, ayırıcının ilk geçtiği yerden böler. "Son" varyantı yoktu, bu yüzden herkes LastIndex ve dilimlemeyi elle yazıyordu ve bizim yaklaşık %30'umuz ilk denemede bir off-by-one hatası yaptı.

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

Dosya uzantıları ve host:port ayrıştırmak için harika; burada son iki nokta üst üste işaretini bulmanız gerekir, çünkü IPv6 adresleri bunlarla doludur.

math/big.Int.Divide. Açık bir yuvarlama moduyla bölme; "Quo mu Div mi istiyorum?" yazı tura atışının yerini alıyor:

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

Yuvarlama kuralının bir yönetmelikte yazılı olduğu bir alanda çalışıyorsanız, bu sizin içindir.

url.URL.Clone ve url.Values.Clone. Derin kopyalar. Eski *u sığ kopyası Userinfo işaretçisini paylaşıyordu ve bu, bulunması bir gün, düzeltilmesi beş saniye sürecek türden bir hataya yol açıyordu.

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

net.UnixConn okuma metotları artık io.EOF değerini net.OpError içine sarmak yerine doğrudan döndürür. err == io.EOF artık çalışıyor, her zaman olması gerektiği gibi.

database/sql.ConvertAssign ve driver.RowsColumnScanner. Sürücü geliştiricilerine yönelik özellikler. Birincisi, Rows.Scan'in gerçekleştirdiği tip dönüşümlerini açığa çıkarır; ikincisi, sürücülerin ara bir ayırmadan kaçınarak doğrudan kullanıcı hedeflerine tarama yapmasına izin verir.

unicode 15 → 17. Tek bir sıçrayışta iki sürüm. Dize sınıflandırması ve normalleştirme davranışı ince biçimde değişebilir. Buna bağlı testleriniz varsa, bunu öğreneceksiniz.

compress/flate daha hızlı hale geldi ve çıktı baytları Go 1.26'dan farklı olabilir. Bu etki archive/zip, compress/gzip, compress/zlib ve image/png paketlerine de yayılır. Sıkıştırılmış çıktıyı hash'leyen golden-file testleriniz varsa, bunlar bozulacak ve bu bir gerileme olmayacak. Yükseltmeden önce kontrol edin, olay incelemesi sırasında değil.

4. Araç Zinciri

4.1. go fix Daha Fazla Modernleştiriciye Sahip

go fix sessizce bir kod modernleştirme aracına dönüşüyor. Go 1.27 atomictypes, embedlit, slicesbackward ve unsafefuncs ekliyor. Önizleme için -diff kullanın:

 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 }

Küçük bir dosyada dört modernleştirici tetiklendi:

  • embedlit: §1.2'deki yeni sözdizimini kullanarak gömülü literal'leri yeniden yazar
  • atomictypes: atomic.AddInt64(&x, 1), atomic.Int64.Add(1) olur; bu, atomik olmayan erişimi imkânsız kılar ve farkında olmadığınız 32 bitlik hizalama hatalarını sessizce düzeltir
  • slicesbackward: geriye doğru döngüler slices.Backward olur
  • waitgroupgo: Add/go/Done ritüeli wg.Go olur (belirsizliği önlemek için 1.26'daki waitgroup adı değiştirildi)

go tool fix help 26 modernleştiricinin tamamını listeler; go tool fix help <name> birini açıklar. Ayrıca fmtappendf, "üslup kaygıları nedeniyle" kaldırıldı; bu, o issue tartışmasında ne olduğunu anlatmanın çok diplomatik bir yoludur.

Eski bir kod tabanında go fix ./... çalıştırmak gerçekten tatmin edici bir öğleden sonra. Önce diff'i okuyun, tabii ki.

4.2. go test Varsayılan Olarak stdversion Çalıştırır

Bu, gününüzü bölme ihtimali en yüksek değişiklik.

go test artık varsayılan olarak stdversion vet kontrolünü çalıştırır; bu kontrol, go.mod dosyanızdaki go yönergesinin izin verdiğinden daha yeni standart kütüphane sembollerini işaretler:

1// go.mod says: go 1.24
2package sv
3
4import "strings"
5
6func Ext(name string) string {
7	_, ext, _ := strings.CutLast(name, ".")  // CutLast is a 1.27 symbol
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]

Bu, her şeyin sizin makinenizde (en güncel araç zinciriyle) çalıştığı ama CI'da ya da bir kullanıcının daha eski ortamında patladığı klasik hata modunu ortadan kaldırır. Muhafazakâr bir go yönergesiyle kütüphane yayınlıyorsanız, bu bir hediye.

4.3. go test -json, OutputType Kazandı

"Action":"output" satırları artık isteğe bağlı bir "OutputType" alanı taşıyor: "error", "error-continue" veya "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'

t.Log çıktısı (alan yok), t.Error çıktısı (error) ve çerçeve tarafından üretilen satırlar (frame) artık ayırt edilebilir. Test çıktısının hangi satırlarının "gerçek" olduğunu anlamak için regex yazmış olan herkes onu artık silebilir.

4.4. go doc İyileştirmeleri

package@version sözdizimi. Modülünüze eklemeden belirli bir sürümün belgelerini okuyun:

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

Yükseltmeden önce neyin değiştiğini kontrol etmek için kullanışlı.

-ex ve örnek kaynağının yazdırılması:

 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

Örnek kaynağı ve beklenen çıktı, gelecek perşembe de açık kalacak bir tarayıcı sekmesini açmadan, terminalde.

4.5. go mod tidy require Bloklarınızı Temizler

go 1.27 veya üstü modüller için go mod tidy, dağınık require bloklarını en fazla iki bloğa (doğrudan ve dolaylı) birleştirir ve ilişkili yorumları korur.

 1// Önce
 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// Sonra: 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)

// networking yorumunun korunduğuna dikkat edin. Bu, çoğunlukla Git birleştirme çakışması çözümünden sonra temizlik yapar; dağınık require blokları tam da orada doğar. Ekibinizde bağımlılık ekleyen üçten fazla kişi varsa, go.mod çakışmalarınız belirgin biçimde daha sessiz hale gelecek.

4.6. Çeşitli Küçük Konular

  • bzr desteği kaldırıldı. Bu sizi etkiliyorsa, hikâyesini gerçekten duymak isterim.
  • go tool trace -http, yalnızca bir port verildiğinde localhost'a bağlanır. -http=:6060 artık her arayüzde dinlemez. Bunu kastettiyseniz -http=0.0.0.0:6060 kullanın. Bu, go tool pprof ile uyumlu hale gelir ve ara sıra yaşanan kazara herkese açık profilleyici sorununu önler.
  • Yanıt dosyaları (@file), compile, link, asm, cgo, cover ve pack tarafından GCC uyumlu bir biçimde desteklenir. Komut satırı uzunluk sınırlarını aşan derleme sistemleri için; merhaba, Bazel.

5. Derleyici, Bağlayıcı, Portlar

Derleyici. //line yönergelerindeki göreli dosya adları artık, go/scanner ile uyumlu olarak, içeren dosyanın dizinine göre çözümlenir. Kod üreticileri yazıyorsanız ilgilidir.

Fonksiyon literal'i (closure) sembol adları da artık daha basit: inline edilip edilmemesinden bağımsız olarak aynı ad kullanılır ve aynı literal'in birden fazla örneği ikili dosyada kodu paylaşabilir. İşlevsel bir değişiklik yok, ancak: fonksiyon kimliğini reflect.Value.Pointer ile karşılaştıran kod, daha önce olduğundan daha sık "eşit" görecek. Bu karşılaştırma hiçbir zaman geçerli değildi, ama bunu kullanıyorsanız, artık daha yüksek sesle yalan söylemeye başlayacak.

Bağlayıcı. Yeni -macos ve -macsdk seçenekleri, macOS LC_BUILD_VERSION yükleme komutundaki OS ve SDK sürümlerini ayarlar.

Portlar.

  • Darwin artık macOS 13 Ventura veya üstünü gerektiriyor, bu 1.26'da duyurulmuştu. CI runner'larınızı kontrol edin.
  • PowerPC (GOOS=linux GOARCH=ppc64) ELFv2 ABI'sine geçti. Linux çekirdeği 3.13+ gerektirir (RHEL7 3.10'a geriye taşındı). Cgo, PIE ve harici bağlama artık destekleniyor. Cgo kullanıyorsanız ama statik, saf Go bir ikili dosyaya ihtiyacınız varsa, CGO_ENABLED=0 ayarlayın.

6. Yükseltme Kontrol Listesi

Go 1.27 uyumluluğu ciddiye alıyor, ama sürümü yükseltmeden önce şunları kontrol edin:

  • Sıkıştırılmış çıktı üzerindeki golden-file testleri. compress/flate kodlayıcılarını değiştirdi; gzip/zip/png baytları farklı olabilir.
  • JSON hata mesajı dizelerini eşleştiren testler. Davranış aynı; hata metni aynı değil.
  • go.mod içindeki kaldırılmış GODEBUG'lar. asynctimerchan, gotypesalias, tlsrsakex, tls3des, tls10server, tlsunsafeekm, x509keypairleaf — bunlar yalnızca eski değerlerine ayarlanmışsa derlemeyi başarısız kılar.
  • stdversion ihlalleri. go test artık bunları yakalıyor. Muhafazakâr bir go yönergesi olan bir kütüphaneyi sürdürüyorsanız, erken çalıştırın.
  • macOS 12 veya daha eski CI runner'ları. Destek kalktı.
  • Fonksiyon literal'i sembol adlarına dayanan testler ve reflect.Value.Pointer fonksiyon karşılaştırmaları.
  • Transport.MaxIdleConns = 0 ya da her istek için bir Client. Otomatik boşaltılan yanıt gövdeleriyle birlikte, bu daha yavaş çalışmaya neden olabilir.

Kapanış Düşünceleri

Go 1.27, birçok ertelenmiş bakımın aynı anda geldiği sürüm.

Genel metotlar, 1.18 generic çalışmasının eksik parçasıydı. Struct literal alan seçicileri on bir yıllık bir issue'yu kapattı. encoding/json/v2 iki yıllık bir öneri tartışmasıydı. Ve uuid, neredeyse her Go projesinin on yıldır aynı üçüncü taraf bağımlılıkla yamaladığı bir boşluğu doldurdu.

Bu sürümden gerçekten değer elde etmek için bir öncelik sırası istiyorsanız:

  1. httptest.NewTestServer + synctest.Sleep: zamana bağlı HTTP testleri hızlı ve deterministik hale gelir. Tüm sürümdeki en iyi çaba-getiri oranı.
  2. Goroutine sızıntı profili: staging'de bir uç nokta açın ve alçakgönüllü olmaya hazırlanın.
  3. Geri izleme goroutine etiketleri: istek giriş noktanızda üç satırlık pprof.Do, bir sonraki sabah 3'teki yığın dökümünüzü okunabilir bir şeye dönüştürür.
  4. go fix ./...: modernleştirme işini araca bırakın.
  5. encoding/json/v2: acele etmeyin. Bir seferde bir seçenek benimseyin.

go1.27rc3'ü alın ve test paketinizi şimdi bununla çalıştırın. Bu kontrol listesindeki her şeyi bir salı öğleden sonrası keşfetmek, bir olay sırasında keşfetmekten çok daha keyifli.

Kaynakça