Mert Özen Her Satırda Daha İleri
Backend Go ile Backend — #06

Go Generics ve Iterator'lar: Type Parameter, Constraint, range-over-func ve Generic Method'lar

Mert Özen 15 Kas 2026 17 dk 3 görüntülenme
Go Generics ve Iterator'lar: Type Parameter, Constraint, range-over-func ve Generic Method'lar

Go'da constraint bir tip kümesidir: ~int64 yazmadığın sürece Money gibi isimli bir tip int64 isteyen generic fonksiyona verilemez. Bookstore'da generic Page[T] ile sayfalama, cmp.Ordered ve comparable, C# generics'ten farklar (variance yok, GC shape stenciling), iter.Seq ile kitapları yazara göre gruplayan iterator, yield tuzağı, LINQ zihniyetiyle kıyas ve Go 1.27'nin generic method'ları.

Sürüm notu: Bu yazıdaki kodlar Go 1.27.4 ile derlenip çalıştırılmıştır; module'ün go satırı 1.26.0'dır ve uygulama kodu Go 1.26.11 ile de derlenir. Iterator'lar (range-over-func) Go 1.23 ile, kendine referans veren constraint'ler Go 1.26 ile gelmiştir. Generic method'lar Go 1.27'ye özgüdür; onları gösteren lab dosyası //go:build go1.27 satırıyla ayrılmıştır.

Önce Tahmin Et

Sitedeki serilerin ortak örnek uygulaması Bookstore'da (bir online kitapçı) fiyatları 03'ten beri int64 tabanlı bir Money tipiyle, kuruş olarak tutuyoruz. Hem stok adetlerini hem fiyatları toplayabilen tek bir generic Sum fonksiyonu yazalım. Constraint'i "int ya da int64" diye verdim:

package main

import "fmt"

type Money int64 // kuruş cinsinden

func Sum[N int | int64](values []N) N {
    var total N
    for _, v := range values {
        total += v
    }
    return total
}

func main() {
    stock := []int{12, 0, 4}
    prices := []Money{34990, 19900}
    fmt.Println(Sum(stock))
    fmt.Println(Sum(prices))
}

Money'nin altında bir int64 var. Program derlenir mi, derlenirse iki satırda ne yazar?

go run .
# guess
./main.go:19:17: Money does not satisfy int | int64 (possibly missing ~ for int64 in int | int64)

Derlenmedi. int | int64 constraint'i tam olarak bu iki tipi kabul ediyor; Money ise underlying type'ı int64 olan ama ondan farklı, isimli bir tip. Derleyici çözümü de söylüyor: ~int | ~int64 yazınca "underlying type'ı int ya da int64 olan her tip" demiş oluyoruz ve program 16 ile 54890 yazıyor. Bu küçük işaret, Go generics'inin C#'tan farklı düşündüğü yeri gösteriyor: constraint'ler bir tip kümesi tanımlıyor. Bu yazıda type parameter'ları, constraint'leri, iterator'ları ve Go 1.27'de method'lara gelen type parameter'ları Bookstore'un katalog ve sayfalama koduyla göreceğiz.

Type Parameter ve Constraint

Go'ya generics 1.18 ile, Mart 2022'de geldi. Bir fonksiyon köşeli parantez içinde type parameter alır, her type parameter'ın bir constraint'i olur. Constraint her zaman bir interface'tir; ama bu interface method listesinin yanında bir tip kümesi de tanımlayabilir. Lab'da üç farklı constraint var:

// Lab 06: type parameter'lar, constraint'ler ve generic paging.Page.
package main

import (
    "cmp"
    "fmt"

    "bookstore/internal/catalog"
    "bookstore/internal/paging"
)

// Integer, underlying type'ı int ya da int64 olan her tiptir;
// bu yüzden catalog.Money gibi isimli tipler de bu kümeye girer.
type Integer interface {
    ~int | ~int64
}

// Sum değerleri toplar.
func Sum[N Integer](values []N) N {
    var total N
    for _, v := range values {
        total += v
    }
    return total
}

// MaxBy key'i en büyük olan item'ı döndürür; boş slice'ta ok false olur.
func MaxBy[T any, K cmp.Ordered](items []T, key func(T) K) (best T, ok bool) {
    for i, it := range items {
        if i == 0 || key(it) > key(best) {
            best, ok = it, true
        }
    }
    return best, ok
}

// IndexBy bir lookup map'i kurar; map key'i olabilmesi için K comparable olmalı.
func IndexBy[K comparable, V any](items []V, key func(V) K) map[K]V {
    m := make(map[K]V, len(items))
    for _, it := range items {
        m[key(it)] = it
    }
    return m
}

Integer bir union: | ile ayrılmış tipler, ~ ile "bu underlying type'a sahip her tip". Böyle bir interface yalnız constraint olarak kullanılabilir, değişken tipi olamaz. cmp.Ordered standart kütüphanenin hazır constraint'i: < ve > operatörlerini destekleyen bütün tam sayı, ondalıklı sayı ve string tipleri. comparable ise == ile karşılaştırılabilen tipler için dilin kendi constraint'i; map key'i olabilmek için gerekli. En gevşek constraint any: hiçbir şey istemez, ama o tipte bir değerle de hiçbir işlem yapamazsın.

go run ./labs/06-generics
sum of prices: 1543.40 TRY
books in stock: 31
most expensive: Dune | longest title: Sense and Sensibility
9780141439686 -> Persuasion
page 2/2, 2 of 6 items, len=2 cap=2
   Persuasion
   Sense and Sensibility
page 9: 0 items, nil=false
paging.Page[bookstore/internal/catalog.Book]
paging.Page[string]

Çağrıların hiçbirinde tip argümanı yazmadım; derleyici onları argümanlardan çıkardı. Sum(prices)'ta N catalog.Money oldu ve sonuç da Money olduğu için Println onun String() method'unu kullandı. MaxBy çağrısında K'yı key fonksiyonunun dönüş tipinden buldu. Kataloğa bu yazıda Jane Austen'ın iki kitabını daha ekledim (Persuasion ve Sense and Sensibility); gruplama örneğinde bir yazarın birden fazla kitabı olsun istedim.

C# Generics ile Farklar

C# Koleksiyonlar ve Generics serisinin "C# Generic Class, Generic Method ve Constraint'ler" yazısını okuduysan syntax dışında birkaç önemli fark göreceksin. C#'ta constraint where T : IComparable<T>, new() gibi bir listeyle verilir ve "bu tip şu interface'i uygular" der. Go'da constraint bir tip kümesidir; ~int64 gibi bir ifadenin C#'ta doğrudan karşılığı yok. .NET 7'deki generic math (INumber<T>) aynı ihtiyaca interface hiyerarşisiyle cevap veriyor.

Variance yok. Aynı serinin "C# Generic Variance" yazısında IEnumerable<string>'in IEnumerable<object>'e atanabildiğini görmüştük. Go'da []string, []any bekleyen bir fonksiyona verilemez; derleyici cannot use titles (variable of type []string) as []any value in argument to show der. İkisinin bellekteki yapısı farklı olduğu için çevirmek bir döngü ister.

Çalışma zamanı modeli de farklı. .NET'te generic tipler runtime'da da var: List<int> için ayrı native kod üretilir, reference type'lar ortak kodu paylaşır, reflection ile tip argümanını görürsün. Go derleyicisi ise başka bir yol izliyor. GC (garbage collector, yani belleği otomatik temizleyen bileşen) açısından aynı bellek şekline sahip tipler (örneğin bütün pointer'lar) tek bir kopya kodu paylaşıyor ve tipe özgü bilgiyi gizli bir sözlük parametresiyle alıyor; Go'nun tasarım dokümanı buna "GC shape stenciling" diyor. Bu yüzden generic kod her zaman elle yazılmış koddan hızlı değil; performans kritikse ölç.

Son olarak Go'da specialization, default(T) yerine geçen bir anahtar kelime ya da new() constraint'i yok. Bir tipin zero value'sunu var zero T ile alırsın; Sum'daki var total N tam olarak bu.

Generic Page[T]: Bookstore'da Sayfalama

Katalog büyüdükçe GET /api/books bütün listeyi tek seferde döndüremez. ASP.NET Core Web API serisinin "Pagination, Filtering ve Sorting" yazısındaki PagedResult<T>'nin Go karşılığını internal/paging package'ına koydum:

// Package paging, API cevapları için listeleri sayfalara böler.
package paging

// Page, daha uzun bir listenin tek bir sayfasıdır.
type Page[T any] struct {
    Items      []T `json:"items"`
    Page       int `json:"page"`
    PageSize   int `json:"pageSize"`
    TotalItems int `json:"totalItems"`
    TotalPages int `json:"totalPages"`
}

// Of, items'ın page numaralı sayfasını (1'den başlar) sayfa başına size item ile döndürür.
// page >= 1 ve size >= 1 kontrolünü çağıran yapar.
func Of[T any](items []T, page, size int) Page[T] {
    total := len(items)
    start := min((page-1)*size, total)
    end := min(start+size, total)
    return Page[T]{
        Items:      items[start:end:end], // cap = len: bir append sonraki sayfanın üzerine yazamaz
        Page:       page,
        PageSize:   size,
        TotalItems: total,
        TotalPages: (total + size - 1) / size,
    }
}

Generic'in burada gerçekten işe yaradığını görmek kolay: sayfalama mantığı kitap, sipariş ya da yazar için birebir aynı. items[start:end:end]'deki üçüncü sayı 03'teki slice header konusundan geliyor: capacity'yi length'e eşitleyerek bu sayfaya yapılan bir append'in yeni bir array açmasını, alttaki kataloğun bir sonraki elemanının üzerine yazmamasını sağlıyor. Lab çıktısındaki len=2 cap=2 bunu gösteriyordu. Son sayfanın ötesi istenirse start ve end listenin uzunluğuna eşitlenir ve boş ama nil olmayan bir slice döner.

Handler, sorgu parametrelerini doğrulayıp sonucu paging.Of'a veriyor:

mux.HandleFunc("GET /api/books", func(w http.ResponseWriter, r *http.Request) {
    q := r.URL.Query()
    list := store.All()
    if s := q.Get("maxPrice"); s != "" {
        limit, err := strconv.ParseInt(s, 10, 64)
        if err != nil {
            http.Error(w, "maxPrice must be a whole number of kurus", http.StatusBadRequest)
            return
        }
        list = store.CheaperThan(catalog.Money(limit))
    }
    page, okPage := positiveInt(q.Get("page"), 1)
    size, okSize := positiveInt(q.Get("pageSize"), 20)
    if !okPage || !okSize || size > 100 {
        http.Error(w, "page and pageSize must be positive whole numbers, pageSize at most 100", http.StatusBadRequest)
        return
    }
    writeJSON(w, paging.Of(list, page, size))
})
// positiveInt, s'yi 1 ya da daha büyük bir tam sayı olarak parse eder. Boş s için def döner.
func positiveInt(s string, def int) (int, bool) {
    if s == "" {
        return def, true
    }
    n, err := strconv.Atoi(s)
    if err != nil || n < 1 {
        return 0, false
    }
    return n, true
}
curl -s 'http://127.0.0.1:52710/api/books?page=2&pageSize=2'
curl -s 'http://127.0.0.1:52710/api/books?page=7&pageSize=2'
curl -i -s 'http://127.0.0.1:52710/api/books?pageSize=500'
{"items":[{"isbn":"9780141439549","title":"Middlemarch","author":"George Eliot","price":31000,"stock":4},{"isbn":"9780141439587","title":"Emma","author":"Jane Austen","price":19900,"stock":7}],"page":2,"pageSize":2,"totalItems":6,"totalPages":3}
{"items":[],"page":7,"pageSize":2,"totalItems":6,"totalPages":3}
HTTP/1.1 400 Bad Request
Content-Type: text/plain; charset=utf-8
X-Content-Type-Options: nosniff
Date: Sun, 15 Nov 2026 01:28:50 GMT
Content-Length: 71

page and pageSize must be positive whole numbers, pageSize at most 100

Bu değişiklikle /api/books'un cevabı bir array'den bir object'e döndü. Yayındaki bir API'de bu bir breaking change olurdu; Bookstore'un Go API'sini henüz kimse kullanmadığı için şimdi yapmak ucuz. Hata cevabı hâlâ düz metin; 10'da Problem Details'e geçeceğiz.

Ne Zaman Generic Yazmamalı?

Ian Lance Taylor'ın Nisan 2022 tarihli "When To Use Generics" yazısındaki öneriyi kendi cümlelerimle aktarayım: birden fazla tip için kod satır satır aynı kalıyorsa (slice ve map üzerinde çalışan fonksiyonlar, genel amaçlı veri yapıları) generic yaz. Tek yaptığın type argument'ın bir method'unu çağırmaksa interface yeter; io.Reader alan bir fonksiyonu [T io.Reader] yapmak hiçbir şey kazandırmaz. Uygulama her tip için farklıysa generic değil, ayrı fonksiyonlar ya da interface method'ları gerekir.

Bu yüzden kitapları yazara göre gruplayan kodu generic bir GroupBy yerine catalog'a özel bir method olarak yazdım. Kodda tek bir kullanım yeri var; generic bir yardımcı, okuyanın üç type parameter'ı kafasında tutmasını isterdi. 02'deki "her şeyi utils package'ına koymak" tuzağının generic hâli de aynı: internal/genericutil diye bir package açmak yerine, ihtiyaç ikinci kez ortaya çıkana kadar bekle.

Iterator'lar: iter.Seq ve range-over-func

Go 1.23 ile for range fonksiyonların üzerinde de dönebiliyor. iter package'ı iki tip tanımlıyor: iter.Seq[V] aslında func(yield func(V) bool), iter.Seq2[K, V] ise iki değerli hâli. Iterator, her değer için yield'i çağıran bir fonksiyon. C# tarafındaki Bookstore.Catalog module'ünün karşılığı olan internal/catalog'a iki iterator ekledim:

// Books her kitabı katalog sırasıyla yield eder. Her değer bir kopyadır.
func (Store) Books() iter.Seq[Book] {
    return slices.Values(books)
}
// ByAuthor her yazarı bir kez, katalogda ilk göründüğü sırayla ve
// o yazarın kitaplarıyla birlikte yield eder.
func (Store) ByAuthor() iter.Seq2[string, []Book] {
    return func(yield func(string, []Book) bool) {
        var authors []string
        groups := make(map[string][]Book)
        for _, b := range books {
            if _, seen := groups[b.Author]; !seen {
                authors = append(authors, b.Author)
            }
            groups[b.Author] = append(groups[b.Author], b)
        }
        for _, a := range authors {
            if !yield(a, groups[a]) {
                return
            }
        }
    }
}

Books, standart kütüphanedeki slices.Values'ı kullanıyor ve slice'ın kendisini dışarı vermeden kitapları tek tek gezdiriyor; 03'te All()'un neden bir kopya döndürdüğünü konuşmuştuk, iterator'da kopya hiç oluşmuyor. ByAuthor ise kendi yazdığımız bir Seq2. Map'in iteration sırası rastgele olduğu için yazar sırasını ayrı bir slice'ta tutuyor; böylece API her istekte aynı sırayı döndürüyor.

Asıl önemli satır if !yield(a, groups[a]) { return }. for range döngüsünün gövdesi derleyici tarafından bir fonksiyona çevrilir ve yield olarak iterator'a verilir. Döngüde break ya da return olursa yield false döndürür ve iterator durmak zorundadır. C#'ta LINQ (Language Integrated Query; C#'ın koleksiyonlar üzerinde sorgu yazma özelliği) serisinin "LINQ'te Kendi Operatörünü Yazmak: Extension Method ve yield return" yazısında gördüğümüz yield return bir state machine üretiyordu; Go'nun yaklaşımı daha basit, sadece bir callback.

// Lab 06: iter.Seq, range-over-func, slices/maps yardımcıları ve bozuk bir iterator.
package main

import (
    "fmt"
    "iter"
    "maps"
    "slices"

    "bookstore/internal/catalog"
)

// Filter, seq'teki değerlerden keep'in true döndürdüklerini yield eder.
func Filter[V any](seq iter.Seq[V], keep func(V) bool) iter.Seq[V] {
    return func(yield func(V) bool) {
        for v := range seq {
            if keep(v) && !yield(v) {
                return
            }
        }
    }
}

// countdown yield'in sonucunu yok sayıyor: runtime'ın yakaladığı bir hata.
func countdown(yield func(int) bool) {
    for i := 3; i > 0; i-- {
        yield(i)
    }
}

func main() {
    var store catalog.Store

    // 1) Döngü gövdesi yield fonksiyonunun kendisi; break, yield'in false döndürmesi demek.
    for b := range store.Books() {
        if b.Stock == 0 {
            fmt.Println("first sold-out book:", b.Title)
            break
        }
    }

    // 2) Seq2 iki değer yield eder: yazar ve kitapları.
    for author, books := range store.ByAuthor() {
        fmt.Printf("%-15s %d\n", author, len(books))
    }

    // 3) Birleştirme: filtreler lazy çalışır, sonuç en sonda slice'a toplanır.
    inStock := Filter(store.Books(), func(b catalog.Book) bool { return b.Stock > 0 })
    cheap := Filter(inStock, func(b catalog.Book) bool { return b.Price < 25000 })
    var titles []string
    for b := range cheap {
        titles = append(titles, b.Title)
    }
    fmt.Println(titles)

    // 4) maps.Keys bir iterator döndürür; map sırası rastgele olduğu için sıralıyoruz.
    stock := map[string]int{}
    for b := range store.Books() {
        stock[b.Title] = b.Stock
    }
    fmt.Println(slices.Collect(maps.Keys(stock))[:3])
    fmt.Println(slices.Sorted(maps.Keys(stock))[:3])

    // 5) break'ten sonra da yield'i çağırmaya devam eden bir iterator.
    defer func() { fmt.Println("recovered:", recover()) }()
    for i := range countdown {
        fmt.Println("got", i)
        break
    }
}
go run ./labs/06-iterators
first sold-out book: The Hobbit
Frank Herbert   1
J.R.R. Tolkien  1
George Eliot    1
Jane Austen     3
[Emma Persuasion Sense and Sensibility]
[Persuasion Sense and Sensibility Dune]
[Dune Emma Middlemarch]
got 3
recovered: runtime error: range function continued iteration after function for loop body returned false

Son satır bir hatayı gösteriyor: countdown, yield'in false döndürmesini görmezden gelip onu tekrar çağırdı ve runtime bunu bir panic'e çevirdi. Kendi iterator'ını yazarken yield'in sonucunu her seferinde kontrol et.

slices, maps ve LINQ Zihniyeti

Standart kütüphane iterator'ların iki ucunu veriyor. Üreten taraf: slices.All, slices.Values, slices.Backward, slices.Chunk, maps.Keys, maps.Values, maps.All. Tüketen taraf: slices.Collect, slices.Sorted, slices.AppendSeq, maps.Collect. Lab'ın dördüncü adımında maps.Keys'in sırası rastgele geldi, slices.Sorted ise her seferinde aynı sırayı verdi. Lab'ı beş kez çalıştırdığımda bu satır üç farklı sırayla geldi ([Dune The Hobbit Middlemarch], [Middlemarch Emma Persuasion]…); map sırasına bağlı bir test bu yüzden bazen geçer, bazen kalır.

Ortada ise bir şey yok: Where, Select, GroupBy gibi adapter'lar standart kütüphanede yok. Lab'daki Filter'ı kendim yazdım ve iki filtreyi iç içe verdim. Bu zincir lazy çalışıyor, LINQ serisinin "LINQ Deferred Execution" yazısında anlattığımız gibi; değerler ancak for range onları istediğinde üretiliyor. Ama Filter(Filter(seq, a), b) okunması zor bir yapı. Go'da aynı iş genellikle tek bir döngü ve birkaç if ile yazılır ve Go topluluğu bunu bir eksik olarak değil, okunabilirlik tercihi olarak görür. 1.27'ye kadar zincirleme syntax'ın teknik bir engeli de vardı: method'lar kendi type parameter'ını alamıyordu, yani books.Map(...) yazılamıyordu.

Go 1.27: Generic Method'lar

Go 1.27 release notes'una göre bir method artık kendi type parameter'larını tanımlayabiliyor. Bookstore'un module'ü go 1.26.0'da olduğu için bu özelliği bir lab'da, dosya seviyesinde bir build constraint ile denedim. //go:build go1.27 satırı, o dosyanın dil sürümünü 1.27'ye çıkarıyor; Go 1.26 ile derlerken dosya hiç derlenmiyor ve yanındaki //go:build !go1.27 dosyası "bu lab Go 1.27 ister" diyor.

//go:build go1.27

// Lab 06: Go 1.27 generic method'ları. Yukarıdaki build constraint bu dosyanın
// dil sürümünü 1.27'ye çıkarır; module'ün kendisi go 1.26.0'da kalır.
package main

import (
    "fmt"
    "math/rand/v2"

    "bookstore/internal/catalog"
)

// List, zincirlenebilir yardımcıları olan bir slice'tır.
type List[T any] []T

// Map her element'i dönüştürür; U, method'un kendi type parameter'ı.
func (l List[T]) Map[U any](f func(T) U) List[U] {
    out := make(List[U], 0, len(l))
    for _, v := range l {
        out = append(out, f(v))
    }
    return out
}

// Where, keep'in true döndürdüğü element'leri tutar.
func (l List[T]) Where(keep func(T) bool) List[T] {
    var out List[T]
    for _, v := range l {
        if keep(v) {
            out = append(out, v)
        }
    }
    return out
}

func main() {
    books := List[catalog.Book](catalog.Store{}.All())
    titles := books.
        Where(func(b catalog.Book) bool { return b.Author == "Jane Austen" }).
        Map(func(b catalog.Book) string { return b.Title })
    fmt.Println(titles, len(titles))

    prices := books.Map(func(b catalog.Book) catalog.Money { return b.Price })
    fmt.Println(prices[:2])

    r := rand.New(rand.NewPCG(1, 2))
    fmt.Println(r.N(10), r.N(catalog.Money(100))) // math/rand/v2: (*Rand).N 1.27'de generic bir method
}
go run ./labs/06-genericmethod
[Emma Persuasion Sense and Sensibility] 3
[349.90 TRY 289.50 TRY]
7 0.61 TRY

Map[U any], List[T]'nin type parameter'ının yanına kendi U'sunu ekliyor ve Where(...).Map(...) zinciri artık yazılabiliyor. Standart kütüphane de ilk örneği veriyor: math/rand/v2'de üst seviye bir fonksiyon olan N, 1.27'de *Rand'in generic method'u oldu; yukarıda aynı method'u hem int hem catalog.Money ile çağırdım. Build constraint'i silip module'ün go 1.26.0 satırıyla derleyince derleyici generic method requires go1.27 or later (-lang was set to go1.26; check go.mod) diyor.

Sınır şu: interface method'ları type parameter alamaz ve generic bir method bir interface method'unu karşılayamaz. Bir interface'e Find[T any](key string) (T, bool) yazınca derleyici interface method must have no type parameters diyor. Sebebi Go'nun implicit interface'leri: hangi tip argümanlarıyla çağrılacağı derleme zamanında bilinemeyen bir method için kod üretmenin yolu yok.

1.27'nin ikinci değişikliği fonksiyon tipi inference'ı: generic bir fonksiyon, uygun bir fonksiyon tipine atandığı ya da çevrildiği her yerde tip argümanı yazmadan kullanılabiliyor. Go 1.26'nın getirdiği kendine referans veren constraint'ler de aynı konunun parçası; artık bir generic tip, kendi type parameter listesinde kendisini kullanabiliyor:

package main

import (
    "cmp"
    "fmt"
)

// Comparer kendi type parameter listesinde kendisine referans veriyor (Go 1.26).
type Comparer[T Comparer[T]] interface {
    Compare(other T) int
}

type Version struct{ Major, Minor int }

func (v Version) Compare(o Version) int {
    return cmp.Or(cmp.Compare(v.Major, o.Major), cmp.Compare(v.Minor, o.Minor))
}

func Latest[T Comparer[T]](a, b T) T {
    if a.Compare(b) >= 0 {
        return a
    }
    return b
}

func main() {
    fmt.Println(Latest(Version{1, 26}, Version{1, 27}))
}

Üretimde Dikkat

Module'ün go satırı her şeyi korumuyor. Inference değişikliğini denerken şunu gördüm: generic Double fonksiyonunu bir struct literal'ında, channel'a gönderirken ve bir conversion'da tip argümanı yazmadan kullanan küçük bir program, go 1.26.0 satırıyla Go 1.27.4'te derlenip 4 6 14 yazdı. Aynı program Go 1.26.11 ile derlenmedi:

package main

import "fmt"

func Double[T ~int | ~float64](v T) T { return v * 2 }

type S struct{ F func(int) int }

func main() {
    s := S{F: Double}
    ch := make(chan func(int) int, 1)
    ch <- Double
    fmt.Println(s.F(2), (<-ch)(3), func(int) int(Double)(7))
}
GOTOOLCHAIN=go1.26.11 go build .
# infer
./main.go:10:12: cannot use generic function Double without instantiation
./main.go:12:8: cannot use generic function Double without instantiation
./main.go:13:47: cannot use generic function Double without instantiation

Generic method'lar go satırına bağlı, bu inference değişikliği ise değil. Kodun Go 1.26 ile de derlenmesi gerekiyorsa (Go aynı anda son iki major sürümü destekliyor) CI'da en eski desteklediğin sürümle de build al. Bu seride her yazının sonunda kodu Go 1.26.11 ile ayrıca derliyorum.

Generic kodun hızı tip argümanına göre değişebilir. Pointer ya da interface tipleriyle çağrılan generic fonksiyonlar sözlük üzerinden çalıştığı için bazen elle yazılmış karşılığından yavaştır. Çok sık çalışan bir döngüde generic koda geçmeden önce 12'de göreceğimiz benchmark'larla ölç.

Iterator'lar bir kez gezilebilir olmak zorunda değil, ama bazıları öyle. Store.Books() her çağrıldığında baştan başlar; bir dosyadan ya da network'ten okuyan bir iterator ise ikinci kez gezilemeyebilir. Döndürdüğün iterator'ın hangisi olduğunu doc comment'te yaz.

Sık Düşülen Tuzaklar

~ işaretini unutmak. Önce Tahmin Et'teki hata bu: int64 yazan bir constraint, Money gibi isimli tipleri dışarıda bırakır. Domain'de isimli tip kullanıyorsan constraint'lerde neredeyse her zaman ~ istersin.

Her yerde any. func Process[T any](v T) yazıp içeride v.(Book) diye type assertion yapıyorsan generic bir şey kazanmadın; ya gerçek bir constraint yaz ya da düz bir parametre kullan.

yield'in sonucunu yok saymak. Lab'daki countdown gibi bir iterator, döngü break ile bittiğinde runtime panic'ine yol açar. Her yield çağrısını bir if ile sar.

Map sırasına güvenmek. maps.Keys bir iterator döndürür ama sırası rastgeledir; JSON cevabında ya da testte sabit bir sıra gerekiyorsa slices.Sorted kullan ya da sırayı ByAuthor'daki gibi ayrıca tut.

Her şeyi generic yapmak. Tek bir tip için yazılmış generic bir fonksiyon, okuyanın işini zorlaştırır ve hiçbir şey kazandırmaz. İkinci kullanım ortaya çıkınca generic'e çevirmek kolay.

Küçük Bir Deneme

★☆☆ labs/06-generics'teki Integer constraint'inden ~ işaretlerini sil ve derleyicinin hangi satırda, hangi mesajla durduğuna bak. Sonra Sum'ı []float64 ile de çağırabilmek için constraint'e ne eklemen gerektiğini bul.

★★☆ catalog.Store'a, stokta olan kitapları yield eden bir InStock() iter.Seq[Book] method'u ekle ve GET /api/books?inStock=true parametresini onunla uygula. İpucu: sonucu paging.Of'a vermeden önce slices.Collect ile slice'a çevirmen gerekecek.

★★★ iter.Pull ile Store.Books()'u bir pull iterator'a çevir ve iki kitabı sırayla next() ile al. stop()'u çağırmayı unutursan ne olur? iter package'ının dokümanını oku ve cevabı bir yorum satırına yaz.

Dil bloğu burada bitiyor. Bir sonraki yazıda Go'nun en çok bilinen tarafına geçiyoruz: goroutine'ler ve channel'lar. Go'nun scheduler'ının goroutine'leri thread'lere nasıl dağıttığını, C#'taki async/await'ten neden farklı olduğunu, unbuffered ve buffered channel'ları, select'i ve "all goroutines are asleep" deadlock'unu Bookstore'un stok kontrolü üzerinden göreceğiz.