GoSuda

Go 1.27 veya Generic Methods'tan Endişelenmeyi Bırakıp Onları Sevmeyi Nasıl Öğrendim

By iwanhae
views ...

Go 1.27'ye Genel Bakış

Go 1.27, Ağustos 2026'da yayınlanıyor ve bu kez dilin kendisi değişti. "Slices paketine yeni bir fonksiyon ekledik" tarzında bir değişiklik değil; gerçekten değişti. Generic methods (jenerik metotlar) artık mevcut. On bir yıllık bir sorun çözüme kavuşturuldu. encoding/json kütüphanesi, sistem çalışırken sessizce yeni bir motorla değiştirildi.

Aşağıdaki her örnek go1.27rc3 (darwin/arm64) üzerinde çalıştırılmıştır. Her çıktı bloğu, hata mesajları da dahil olmak üzere kopyala-yapıştır yapılmış gerçek çıktılardır. Eğer bir şeyler tuhaf görünüyorsa, bunun sebebi derleyicinin verdiği gerçek yanıtın bu olmasıdır.

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

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

1.1. Generic Methods (Jenerik Metotlar)

Go 1.18'den beri jeneriklerimiz ve yine Go 1.18'den beri "The Conversation" (Tartışma) mevcut. Tartışma şu şekilde ilerliyor:

"Slice tipim üzerinde sadece bir Map metodu yazmama izin verin—"

method must have no type parameters (metot tip parametrelerine sahip olamaz)

"...pekala. O halde paket seviyesinde bir fonksiyon olarak kalsın."

Metotlar yalnızca receiver (alıcı) tarafından bildirilen tip parametrelerini kullanabiliyordu. Kendi metodunuz yenilerini tanımlayamıyordu. Bu nedenle, her jenerik dönüşüm paket kapsamına sürgün edildi ve orada MapSlice varyasyonları ile isimlendirilmiş diğer on yedi yardımcı fonksiyonun yanında yer aldı.

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 jenerik olması bile gerekmiyor. Sıradan bir struct, jenerik 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üzenlemeden önce, iki kısıtlama olduğunu ve bunların göründüğünden daha önemli olduğunu belirtmek gerekir:

  1. Interface metotları tip parametreleri bildiremez.
  2. Jenerik metotlar interface metotlarını uygulayamaz (implement edemez).

İkinci kısıtlama sizi zorlayacak olanıdır:

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 jenerik metotlar ve dinamik gönderim (dynamic dispatch) bir arada çalışmaz. Bu, Go ekibinin cimriliğinden değil; jenerik bir metodun sonsuz sayıda örneği olabileceğinden ve bir interface metot tablosunun derleme zamanında sonlu ve biliniyor olması gerektiğinden kaynaklanmaktadır. Sonsuz bir şeyi sonlu bir tabloya koyamazsınız. Evren buna izin vermedi.

Pratik okuma: jenerik metotlar, yardımcı program tarzında API'lere sahip somut tipler içindir. Bir interface arkasına gizlenmesi gereken her şey hala eski yaklaşıma ihtiyaç duyar.

Standart kütüphane halihazırda bundan faydalandı. math/rand/v2 paketindeki Rand yapısı jenerik bir metoda kavuştu:

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 sadece paket seviyesindeki rand.N jenerikti, bu da küresel kaynağın (global source) kullanılması anlamına geliyordu. Artık kendi seed değerinize sahip *Rand nesneniz de aynı kolaylığa sahip. Küçük bir şey ama hoş bir şey.

1.2. Struct Literal Field Selectors veya: #9859 Nolu Sorun Nihayet Çözüldü

Embedding (gömme) size u.ID erişimi sağlar. Ancak embedding, User{ID: 1} şeklinde bir kullanım sağlamaz. Bunun yerine User{Base: Base{ID: 1}} yazmanızı gerektirir; bu da Go'nun gerçekten bunu kastedip kastetmediğinizi sorma biçimidir.

#9859 nolu sorun 2015 yılında açılmıştı. Şimdi ise kapatıldı. Bir yerlerde, o günden bu yana kariyerini iki kez değiştirmiş bir gopher, GitHub bildirimi alıyor.

 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: promoted field (terfi ettirilmiş alan) doğrudan kullanılır
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 gerekirdi.

Şimdi ise tümünü derlemeye çalışarak kontrol ettiğim ince detaylara gelelim.

(a) Anahtar hala düz bir tanımlayıcıdır. Keyfi bir selector yolu yazamazsınız.

1_ = Line{Object.name: "x"}   // struct literal içinde geçersiz alan adı Object.name
2_ = Line{p.x: 1}             // struct literal içinde geçersiz alan adı p.x

Yani özellik "anahtarlar artık alan erişimi gibi çalışıyor" demek değildir. Spesifik olarak "örtük olarak terfi ettirilmiş alan adlarına izin verilir" anlamına gelir. Başlıktan biraz daha dar bir kapsamı var.

(b) Aynı anda hem gömülü bir alanı hem de ondan terfi ettirilmiş bir alanı belirtemezsiniz.

1obj := Object{"edge", "black"}
2_ = Line{Object: obj, name: "diagonal"}
3// terfi ettirilmiş alan adı ve kapsayan gömülü alan Object aynı anda belirtilemez

Makul. Ona aynı bellek bölgesi hakkında çelişkili iki bilgi verdiniz ve tahmin yürütmeyi reddetti.

(c) Pointer embedding (işaretçi gömme) dahil edilmedi.

1type PtrEmbed struct {
2	*Object
3	z int
4}
5
6_ = PtrEmbed{name: "x"}
7// name'e ulaşmak için geçersiz örtük işaretçi yönlendirmesi

Bir işaretçiyi takip etmek için takip edilecek bir işaretçiye ihtiyacınız vardır ve literal oluşturma anında henüz böyle bir şey yoktur. Bu da adil.

İyi haber: go fix eski literallerinizi sizin yerinize yeniden yazacak. Bununla ilgili daha fazla bilgi sonra.

1.3. Function Type Inference (Fonksiyon Tip Çıkarımı) Daha Az Keyfi Hale Geldi

Jenerik fonksiyonlar için tip çıkarımı, bazı yerlerde çalışırken diğerlerinde çalışmıyor ve bunun arkasında ayırt edilebilir bir prensip bulunmuyordu. Bir değişkene atama mı? Sorun yok. Aynı fonksiyonu bir struct alanına koymak mı? Derleyici hatası, lütfen 2022 yılındaymışız gibi double[int] yazın.

Go 1.27, hedef tipin belirsiz olmadığı 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] gerekiyordu
 8	a := A{double}             // 1.26: double[int] gerekiyordu
 9
10	c := make(chan func(int) int, 1)
11	c <- double                // 1.26: double[int] 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, bağlamdan tamamen yararlanarak üç yerde func(int) int ve dördüncü bir yerde func(float64) float64 olarak örneklendi. Eğer fonksiyon değerleriyle dolu option struct'ları veya handler tabloları yazıyorsanız, kod tabanınızdan bir yığın [T] gürültüsü silinmek üzere.

2. Runtime: Bedava Performans ve Sızıntı Dedektörü

2.1. Size-Specialized Allocation (Boyuta Özel Tahsis)

Derleyici artık küçük nesneler için boyuta özel tahsis rutinlerine çağrılar yayıyor. Sürüm notları, 80 baytın altındaki tahsislerde %30'a varan, tahsis ağırlıklı programlarda ise genel olarak %1'lik bir kazanç iddia ediyor.

Ölçmeden sürüm notlarına inanmam, 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# varsayılan (size-specialized malloc açık)
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 sadece tahsis yapan bir mikro benchmarkta 30-35% daha hızlı. Yani: bu en iyi durum senaryosu, laboratuvar koşullarında rakamın iyi görünmesi için tasarlanmış bir benchmark ile elde edildi. Gerçek bir programda, GC ve asıl iş yükü bunun çoğunu gölgeleyecektir. %1 civarındaki iddia dürüst olandır.

Bunun maliyeti ikili dosya boyutudur. Hello World:

12413202 bayt  (varsayılan)
22361826 bayt  (nosizespecializedmalloc)

Programınızdan bağımsız olarak sabit yaklaşık 50KB. GOEXPERIMENT=nosizespecializedmalloc ile bunu devre dışı bırakabilirsiniz, ancak bu kaçış yolu Go 1.28'de kaldırılmak üzere planlanmıştır, bu yüzden bunu bir yaşam tarzı değil, hata raporu geçici çözümü olarak değerlendirin.

2.2. Goroutine Leak Profile (Gorutin Sızıntı Profili) Artık Gerçek

Go 1.26'da deneysel olan bu özellik, 1.27'de genel kullanıma açıldı. goroutineleakprofile GOEXPERIMENT'i kaldırıldı; artık doğrudan çalışıyor.

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

Üç gorutin, kalıcı olarak takılı kalmış ve tam olarak nerede sızıntı yaptığınızı gösteren bir dosya ve satır numarası ile birlikte.

Bunun arkasındaki hile gerçekten zekice: garbage collector'ün erişilebilirlik analizini yeniden kullanıyor. Eğer G gorutini P ilkelinde (primitive) engellenmişse ve P, herhangi bir çalışabilir gorutinden (veya o gorutinlerin uyandırabileceği herhangi bir şeyden) ulaşılamıyorsa, o zaman hiçbir şey P'ye bir daha dokunamaz, bu yüzden G asla uyanmayacaktır. Bu bir buluşsal yöntem (heuristic) değil — bu bir kanıttır.

Aynı tasarım size kısıtlamayı da bedavaya sunar: eğer kanal veya mutex bir global değişken aracılığıyla veya hala çalışan bir gorutinin yerel değişkeni aracılığıyla erişilebilirse, GC onu hala görebilir, bu yüzden runtime herhangi bir sonuca varamaz. Yukarıdaki basit örneğin bile raporlama yapabilmesi için iki runtime.GC() çağrısına ihtiyaç duyduğuna dikkat edin. Her şeyi yakalamayacaktır.

Ancak, klasik "context'i iptal etmeyi unuttum, worker gorutin sonsuza kadar yaşıyor" durumunu yakalayacaktır — ki bu, sızıntıların çoğunun nedenidir.

net/http/pprof import ederseniz, /debug/pprof/goroutineleak adresinde de mevcuttur. Bunu staging ortamına bağlayın, haftalık kontrol edin ve sessizce dehşete düşün.

Bu çalışma, bir içecek ikram edilmeyi hak eden, Uber'den Vlad Saioc tarafından katkıda bulunulmuştur.

2.3. Traceback'ler Artık Hangi İsteğin Öldüğünü Söylüyor

Go 1.27 veya üzerini bildiren modüller için, traceback başlık satırları artık runtime/pprof gorutin etiketlerini içerir.

 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 detay: etiketler alt gorutinlere miras kalır. Gorutin 3 asla bir etiket belirlemedi. Ebeveyninden bir tane aldı.

Bu yüzden bir sonraki üretim ortamı kilitlenmesinde ve biri sürece SIGQUIT gönderdiğinde, hepsi birbirine benzeyen 4.000 gorutin yerine, hangi isteğe ve hangi kiracıya ait olduklarıyla etiketlenmiş 4.000 gorutin elde edersiniz. İstek giriş noktanızda üç satırlık pprof.Do size bunu kazandırır.

Etiketler stderr'e dökülmesini istemediğiniz şeyler içerebileceğinden, GODEBUG=tracebacklabels=0 bunu kapatır ve bu kapatma seçeneği kalıcı olarak orada kalması amaçlanmıştır.

2.4. asynctimerchan Artık Tamamen Gitti

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

İlginç olan kısım, bununla birlikte tanıtılan politikadır. go.mod dosyanızda bırakılan kaldırılmış bir GODEBUG, build işlemini otomatik olarak bozmaz; sadece eski değere ayarlanmışsa bozar:

1# go.mod içeriği: 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 içeriği: godebug asynctimerchan=0
6(derleme başarılı)

Bu da demek oluyor ki, sadece kaldırılan davranışa güvenen insanlar uyarılacaktır. Onu nihai varsayılana ayarlayıp unutan herkes, üzerinde düşünmemeye devam eder. Bu, API arkeolojisinin düşünceli bir parçasıdır.

3. Standart Kütüphane

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

Bu büyük bir olay. İki yılı aşkın teklif tartışması nihayet sonuca ulaştı.

Artık üç paket var ve ayrımı anlamak savaşın yarısıdır:

PaketGörev
encoding/jsonBildiğiniz v1 API. 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. Token akışı olarak JSON

Ana haber, encoding/json kütüphanesinin tamamen farklı bir uygulamayla yeniden inşa edilmiş olması ve aynı şekilde davranmasıdır. İşte yolu:

1// Go 1.27'nin 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çine paketlendi ve v1 API her zaman bu paketi uygular. Bu da demek oluyor ki, 1.26 ve 1.27'de şu aynı şekilde davranır:

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

Evet, v1 hala mükerrer anahtarları sessizce kabul eder ve sonuncusunu alır. Her zaman öyleydi. Hala öyle. Uyumluluk, kötü kısımlarla da uyumluluk demektir.

v2 ise görüş sahibidir:

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

Mükerrer anahtarlar ve geçersiz UTF-8 reddedilir. Her ikisi de sadece düzensiz değil, gerçekten tehlikelidir — iki ayrıştırıcı hangi mükerrer anahtarın kazandığı konusunda anlaşamadığında, güvenlik açıkları elde edersiniz. CouchDB'nin CVE-2017-12635 hatası tam olarak buydu: iki roles anahtarına sahip bir JSON gövdesi; doğrulayıcı birini okurken depolama katmanı diğerini okudu. Reddetmek doğru karardır.

Seçenekler variadiktir:

 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ıralı harita anahtarları + girintileme
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 Decoder ve DisallowUnknownFields() gerektiriyordu
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üphane veya el yazısı MarshalJSON ile çözdüğünüz çoğu şey artık bayraklardır.

Ve geçiş hikayesi en iyi kısımdır. Son seçenekler kazanır, bu yüzden v2'nin sıkılığını her seferinde bir davranış olarak benimseyebilirsiniz:

1// v1 semantiğini koru, ancak v2 gibi mükerrer anahtarları reddet
2jsonv2.Unmarshal(data, &v,
3	json.DefaultOptionsV1(),
4	jsontext.AllowDuplicateNames(false))
5// duplicate object member name

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

Performans: marshaling kabaca eşittir, unmarshaling önemli ölçüde daha hızlıdır. Eğer ters giderse, GOEXPERIMENT=nojsonv2 eski uygulamayı geri getirir — ve bu kaçış yolu da sonunda kesilmek üzeredir, bu yüzden yerleşmek yerine bir sorun kaydı açın.

jsontext düşük seviyeli katmandır: Encoder/Decoder, JSON'u Tokenlar ve Valuelar olarak gezerken bir durum makinesi sizi dürüst tutar. Akış dönüştürücü veya JSON filtresi yazarken ve Go değerlerini hiç materyalleştirmek istemediğinizde buna başvurun.

3.2. Standart Bir uuid Paketi

Nihayet. RFC 9562, standart kütüphanede, go get gerektirmiyor.

 1import "uuid"
 2
 3func main() {
 4	fmt.Println(uuid.NewV4())
 5	// b97aa695-da08-472c-af81-ff088129019f
 6
 7	// v7: ilk 48 bit 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 New, NewV4, NewV7, Nil, Max, Parse, MustParse ve bir tipten ibaret: type UUID [16]byte. Hepsi bu kadar. Kahveniz soğurken tüm paket dokümantasyonunu okuyabilirsiniz.

Bilinmesi gereken detaylar:

  • UUID bir [16]byte dizisidir, bu yüzden == çalışır ve doğrudan bir harita anahtarı olarak kullanılabilir. google/uuid ile aynı tasarım.
  • Nil ve Max değişkendir, fonksiyon değildir. Çünkü paket seviyesinde bir var Nil UUID ayağınıza doğrultulmuş dolu bir silahtır ve birileri, bir yerlerde, eninde sonunda ona atama yapacaktır.
  • Rastgele bitler kriptografik olarak güvenli bir üreteçten gelir.
  • encoding.TextMarshaler/TextUnmarshaler/TextAppender arabirimlerini uygular, bu yüzden doğrudan JSON struct'larına düşer.
  • NewV7 veritabanı birincil anahtarları için istediğinizdir. Zamana göre sıralı olması, B-tree indeksinizin parçalanmayı durdurması anlamına gelir; v4 UUID'lerin çok kötü olduğu bir durumdur.

Artık bir bağımlılığı silebilirsiniz. v1/v3/v5 veya daha süslü ayrıştırma seçeneklerine ihtiyacınız varsa, üçüncü taraf kütüphanelerin hala yapacak bir işi var.

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

Go 1.24 bize 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

Şu rakamlara bakın. Tek bir imza 3.309 bayttır. Ed25519 64 bayttır. Bu 50 kat artış demektir ve üzerine bir de 2KB'lık genel anahtar eklenir. Bunlardan birkaçını bir sertifika zincirine tıkıştırırsanız, TLS el sıkışmanız kendi MTU stratejisine ihtiyaç duymaya başlar.

Bugün kuantum direncinin gerçek maliyeti budur ve kimsenin yarın her şeyi değiştirmemesinin nedeni budur. Ancak artık standart kütüphanede, yani ihtiyaç duymadan önce olmasını istediğiniz yerde.

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

PrivateKey, crypto.Signer arabirimini uygular, bu yüzden mevcut arabirimlere yerleşir. crypto/x509 ML-DSA anahtarlarını ve imzalarını işler ve crypto/tls TLS 1.3'te MLDSA44/MLDSA65/MLDSA87 imza şemalarını destekler.

Ayrıca rastgeleliği atlayan SignDeterministic de var — testler ve yeniden üretilebilir build'ler için kullanışlıdır.

3.4. simd: Assembly Olmadan Vektör Talimatları

Go 1.26, mimariye özgü simd/archsimd paketini bir deney olarak tanıttı. Go 1.27, taşınabilir ve vektör genişliğinden bağımsız olan simd paketini ekliyor. 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/depolama 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() sorgusu yaparsınız. VectorBitSize() size gerçek genişliği söyler, Emulated() ise gerçek donanım mı yoksa nazik bir yazılım taklidi mi aldığınızı söyler. Mac'im 128-bit NEON bildirdi; bir AVX-512 makinesi daha fazlasını bildirir; hiçbir şeyi olmayan bir makine emülasyon bildirir ve kod hala çalışır.

LoadFloat32sPart/StorePart özel bir ilgiyi hak ediyor. Elle yazılmış SIMD'in en sinir bozucu kısmı her zaman dizinin sonundaki düzensiz kuyruktur ve bu, ayrı bir skaler döngü olmadan bunu halleder.

Hala deneysel, API hala kararsız, üretime koymayın. Ancak bunun Go kodu olması ve assembly veya cgo olmaması 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 arabirim:

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

Neden? Go'nun yerleşik haritası sadece comparable anahtarları kabul eder. Slice üzerinde anahtarlama yapamazsınız. "Küçük-büyük harf duyarsız eşit" tanımı yapamazsınız. Hasher her ikisini de aynı anda çö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ı seed, yoksa bunun bir anlamı kalmaz
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)

Püf noktası: Hasher bir arabirimdir, bir veri yapısı değildir. Henüz onu tüketen standart bir hash tablosu veya Bloom filtresi yoktur. Bu, gelecekteki bir konteyner paketi için temel oluşturmaktadır. Bugün bunu kendi yapınızı kurarken kullanabilir veya go/types.Hasher (ki bu types.TypeIdentical'a saygı göstererek bir harita anahtarı olarak kullanmanızı sağlar) gibi mevcut bir uygulamayı tüketebilirsiniz.

Seed yönetimi size aittir. Eğer yukarıdaki hashOf her çağrıda taze bir seed oluştursaydı, özdeş değerler farklı hash'lenirdi ve örnek false yazdırırdı — ki bu da ilk denememde yazdığım hatanın ta kendisi. Konteyner başına bir seed. (Seed, hash-flooding DoS saldırılarını yenmek için rastgeleleştirilir, bu yüzden sadece bir sabit değildir.)

3.6. httptest.NewTestServer + synctest.Sleep: Gizli Kahraman

Bu sürümden sadece bir şeyi benimseyecekseniz, bu olsun.

testing/synctest Go 1.25'te mezun oldu ve eşzamanlı kodları test etmek için bize sahte bir saat verdi. Ancak testiniz gerçek bir ağa dokunduğu anda yanılsama çöktü. Go 1.27'nin httptest.NewTestServer fonksiyonu bellek içi sahte bir ağ kullanır, bu yüzden balon bozulmadan kalır. synctest.Sleep (= time.Sleep + synctest.Wait) ise bunu tamamlar.

İşte üstel geri çekilme (exponential backoff) içeren bir 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 tamamladı. Ve iddia elapsed == 3*time.Secondtam olarak üç saniye, "en az üç saniye, scheduler jitter'ı bir yana" değil. Sahte saatler jitter yapmaz.

synctest.Sleep belirli bir nedenle var: testiniz test edilen kodla aynı süre boyunca uyursa, hangisinin önce uyanacağı tahmin edilemez. synctest.Sleep uyur ve sonra balondaki diğer her gorutin dayanıklı bir şekilde engellenene kadar bekler, böylece sistemi yerleştikten sonra gözlemlersiniz.

Zaman aşımları, yeniden denemeler, devre kesiciler, hız sınırlayıcılar — sahip olduğunuz her zamana bağlı HTTP istemci testi hızlı ve deterministik hale gelebilir. Eğer test paketiniz şu anda time.Sleep(100 * time.Millisecond) ve umutla bir arada tutuluyorsa, çıkış yolunuz budur.

3.7. Üretim Ortamını Doğrudan Etkileyen net/http Değişiklikleri

Sessiz, ancak metriklerinizde görünecekler.

HTTP/1 yanıt gövdeleri Close'da otomatik boşaltılır. Okunmamış içerik artık (muhafazakar bir sınıra kadar) kapatıldığında 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ığı şu efsunlu sözleri 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 bir no-op veya küçük bir kazanımdır. Eğer işleri kötüleştiriyorsa, muhtemelen sürüm notlarının nazikçe tanımladığı kovadasınızdır: Transport.MaxIdleConns 0 olarak ayarlanmış veya istek başına yeni bir Client oluşturulmuş, boşta bağlantı sınırını tamamen atlıyor. Transport.DisableKeepAlives = true bunu halının altına süpürecektir, ancak sürüm notlarının gerçek tavsiyesi "daha derin bir bakışın muhtemelen faydalı olacağı" yönündedir, bu Go ekibinin dilinde daha büyük sorunlarınız var demektir.

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

Server.MaxHeaderValueCount. Sunucunun kabul edeceği başlık değeri sayısını DefaultMaxHeaderValueCount varsayılanıyla sınırlandırır. "On bin başlık gönder ve ne olduğuna bak" durumuna kapatılan bir kapı daha.

Kullanıcı tarafından sağlanan bağlantılarda ALPN. Eğer net.Conn yapınız ConnectionState() tls.ConnectionState uyguluyorsa, Transport ve Server üzerinde TLS ALPN müzakeresi yapacaktır — böylece bir proxy üzerinden geçen özel dialer'lar hala HTTP/2 müzakeresi yapabilir.

3.8. Neşe Saçan Küçük Şeyler

strings.CutLast / bytes.CutLast. Cut ilk ayırıcıda böler. "Son" varyantı yoktu, bu yüzden herkes LastIndex artı dilimleme ile kendi yöntemini geliştirdi ve yaklaşık %30'umuz ilk denemede bir hata yaptı.

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

Dosya uzantıları ve host:port ayrıştırma için harika — ki burada son iki noktayı bulmalısınız, çünkü IPv6 adresleri onlarla doludur.

math/big.Int.Divide. "Hangi Quo veya Div'i istiyorum?" yazı tura atışını değiştiren, açık bir yuvarlama moduna sahip bölme:

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

Eğer 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 (Deep copies). Eski *u sığ kopyası Userinfo işaretçisini paylaşıyordu, bu da tam olarak bulunması bir gün, düzeltilmesi beş saniye süren hata türünü üretiyordu.

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 net.OpError içinde sarmalamak yerine doğrudan io.EOF 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ü yazarı özellikleri. İlki Rows.Scan'in gerçekleştirdiği tip dönüşümlerini açığa çıkarır; ikincisi sürücülerin ara bir tahsisi atlayarak doğrudan kullanıcı hedeflerine tarama yapmasını sağlar.

unicode 15 → 17. Tek bir atlamada iki sürüm. Dize sınıflandırma ve normalleştirme davranışı ince bir şekilde değişebilir. Buna bağlı testleriniz varsa, öğreneceksiniz.

compress/flate hızlandı — ve çıktı baytları Go 1.26'dan farklı olabilir. Bu archive/zip, compress/gzip, compress/zlib ve image/png paketlerine yayılır. Sıkıştırılmış çıktıyı hash'leyen altın dosya (golden-file) testleriniz varsa, bunlar bozulacaktır ve bu bir gerileme (regression) olmayacaktır. Yükseltmeden önce, olay incelemesi sırasında değil, bunu kontrol edin.

4. Toolchain (Araç Zinciri)

4.1. go fix Daha Fazla Modernleştiriciye Kavuştu

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 çalıştı:

  • embedlit — yeni sözdizimini kullanarak gömülü literalleri yeniden yazar
  • atomictypesatomic.AddInt64(&x, 1) ifadesini atomic.Int64.Add(1) haline getirir, bu da atomik olmayan erişimi imkansız kılar ve sahip olduğunuzu bilmediğiniz 32-bit hizalama hatalarını sessizce düzeltir
  • slicesbackward — geriye doğru döngüleri slices.Backward haline getirir
  • waitgroupgoAdd/go/Done ritüelini wg.Go haline getirir (belirsizliği önlemek için 1.26'nın waitgroup isminden yeniden adlandırıldı)

go tool fix help tüm 26'sını listeler; go tool fix help <name> birini açıklar. Ayrıca fmtappendf, "üslup kaygıları nedeniyle" kaldırıldı, bu da o sorun başlığında ne yaşandıysa tanımlamanın güzel diplomatik bir yoludur.

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

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

Bu, gününüzü bölme olasılığı en yüksek değişikliktir.

go test artık varsayılan olarak stdversion vet kontrolünü çalıştırır ve go.mod dosyanızın go direktifinin izin verdiğinden daha yeni standart kütüphane sembollerini işaretler:

1// go.mod şunları söyler: go 1.24
2package sv
3
4import "strings"
5
6func Ext(name string) string {
7	_, ext, _ := strings.CutLast(name, ".")  // CutLast bir 1.27 sembolüdür
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 makinenizde (en son araç zinciri) çalıştığı ve CI'da veya kullanıcının daha eski bir ortamında patladığı klasik hata modunu öldürür. Muhafazakar bir go direktifiyle kütüphaneler yayınlıyorsanız, bu bir hediyedir.

4.3. go test -json OutputType Kazandı

"Action":"output" satırları artık isteğe bağlı bir "OutputType" alanı taşır: "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 oluşturulan 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 yazan herkes artık onu silebilir.

4.4. go doc İyileştirmeleri

package@version sözdizimi. Modülünüze eklemeden belirli bir sürüm için dokümanları okuyun:

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

Yükseltme öncesi nelerin değiştiğini kontrol etmek için kullanışlıdır.

-ex ve örnek kaynak yazdırma:

 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 kaynak ve beklenen çıktı, terminalde, gelecek Perşembe hala açık olacak bir tarayıcı sekmesi açmadan.

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

go 1.27 veya sonraki sürümlerdeki modüller için, go mod tidy dağınık require bloklarını en fazla iki taneye (doğrudan ve dolaylı) birleştirir, ekli 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 hayatta kaldığına dikkat edin. Bu çoğunlukla, başıboş require bloklarının doğduğu yer olan Git merge çakışması çözümünden sonra temizlik yapar. Ekibinizde bağımlılık ekleyen üçten fazla kişi varsa, go.mod çakışmalarınız belirgin şekilde sessizleşmek üzere.

4.6. Diğerleri

  • bzr desteği kaldırıldı. Eğer bu sizi etkiliyorsa, hikayeyi gerçekten duymak isterim.
  • go tool trace -http sadece bir port verildiğinde localhost'a bağlanır. -http=:6060 artık her arayüzü dinlemiyor. Eğer kastınız buysa -http=0.0.0.0:6060 kullanın. Bu artık go tool pprof ile eşleşir ve ara sıra kazara oluşan genel profiler'ı engeller.
  • Yanıt dosyaları (@file), GCC uyumlu bir formatta compile, link, asm, cgo, cover ve pack tarafından desteklenir. Komut satırı uzunluk sınırlarını aşan build sistemleri için — merhaba, Bazel.

5. Derleyici, Linker, Portlar

Derleyici. //line direktiflerindeki göreceli dosya adları artık go/scanner ile eşleşecek şekilde içeren dosyanın dizinine göre çözümlenir. Kod üreteçleri yazıyorsanız alakalıdır.

Fonksiyon literal (closure) sembol adları da artık daha basittir — inline işleminden bağımsız olarak aynı isim ve aynı literal'ın birden fazla örneği ikili dosyada kodu paylaşabilir. İşlevsel bir değişiklik yoktur, şu hariç: reflect.Value.Pointer aracılığıyla fonksiyon kimliğini karşılaştıran kod, öncekinden daha sık "eşit" görecektir. Bu karşılaştırma asla geçerli değildi, ancak sahipseniz, şimdi size daha yüksek sesle yalan söylemeye başladığı zamandır.

Linker. Yeni -macos ve -macsdk seçenekleri, macOS LC_BUILD_VERSION yükleme komutundaki OS ve SDK sürümlerini ayarlar.

Portlar.

  • Darwin artık 1.26'da duyurulduğu gibi macOS 13 Ventura veya üzerini gerektirir. CI koşucularınızı kontrol edin.
  • PowerPC (GOOS=linux GOARCH=ppc64) ELFv2 ABI'ye geçti. Linux kernel 3.13+ gerektirir (RHEL7 3.10'a backport edildi). Cgo, PIE ve harici bağlama artık desteklenmektedir. Cgo kullanıyorsanız ancak statik bir saf Go ikili dosyasına ihtiyacınız varsa, CGO_ENABLED=0 ayarlayın.

6. Yükseltme Kontrol Listesi

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

  • Sıkıştırılmış çıktı üzerinde altın dosya testleri. compress/flate kodlayıcıları değiştirdi; gzip/zip/png baytları farklı olabilir.
  • JSON hata mesajı dizeleriyle eşleşen testler. Davranış aynıdır; hata metni değildir.
  • go.mod içinde kaldırılan GODEBUG'lar. asynctimerchan, gotypesalias, tlsrsakex, tls3des, tls10server, tlsunsafeekm, x509keypairleaf — bunlar sadece eski değerlerine ayarlandığında build işlemini başarısız kılar.
  • stdversion ihlalleri. go test artık bunları yakalıyor. Muhafazakar bir go direktifiyle bir kütüphane yönetiyorsanız, erken çalıştırın.
  • macOS 12 veya daha eski CI koşucuları. Destek bitti.
  • Fonksiyon literal sembol adları üzerinde iddiada bulunan testler ve reflect.Value.Pointer fonksiyon karşılaştırmaları.
  • Transport.MaxIdleConns = 0 veya istek başına bir Client. Otomatik boşaltılan yanıt gövdeleriyle birleştiğinde, bu daha yavaş olabilir.

Kapanış Düşünceleri

Go 1.27, bir sürü ertelenmiş bakımın aynı anda vadesinin dolduğu sürümdür.

Jenerik metotlar, 1.18 jenerik çalışmalarının eksik parçasıydı. Struct literal alan seçicileri on bir yıllık bir sorunu kapattı. encoding/json/v2 iki yıllık bir teklif tartışmasıydı. Ve uuid, on yıldır neredeyse her Go projesinin aynı üçüncü taraf bağımlılığıyla yamadığı bir deliği doldurdu.

Bundan gerçek 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-kazanç oranı.
  2. Gorutin sızıntı profili — staging ortamında bir endpoint açın ve mütevazı olmaya hazırlanın.
  3. Traceback gorutin etiketleri — istek giriş noktanızda üç satırlık pprof.Do, bir sonraki gece 03:00 yığın dökümünüzü okunabilir bir şeye dönüştürür.
  4. go fix ./... — bırakın araç modernleştirmeyi yapsın.
  5. encoding/json/v2 — acele etmeyin. Her seferinde bir seçenek olarak benimseyin.

go1.27rc3 sürümünü alın ve test paketinizi hemen üzerinde çalıştırın. O kontrol listesindeki her şeyi Salı öğleden sonra keşfetmek, bir olay sırasında keşfetmekten önemli ölçüde daha keyiflidir.

Referanslar