kodla.cc

Go sözlüğü

133 terim. Derslerde altı kesik çizgili kelimelerin üzerine gelince de görünür.

adreslenebilir

Adresi alınabilen değer: değişken, slice elemanı, pointer'ın gösterdiği alan. Literal, fonksiyon sonucu ve map elemanı değil.

x.Artir() (pointer alıcılı metot) yazdığında Go (&x).Artir() yapar; bunun için x'in adreslenebilir olması gerekir. Sayac{}.Artir(), fonksiyon().Artir() ve m["a"].Artir() derlenmez: cannot call pointer method.

alan

struct'ın içindeki adlandırılmış değer. `k.Ad` ile erişilir.

Her alanın bir adı ve tipi vardır. Büyük harfle başlayan alanlar paket dışından görünür (dışa aktarılır); küçük harfliler görünmez. Değer verilmeyen alan sıfır değerini alır.

alıcı

Metodun hangi tipe bağlı olduğunu gösteren parantezli parametre: `(d Dikdortgen)`.

Alıcı, metodun içinde üzerinde çalıştığı değerin adıdır. Genellikle tipin kısa, tek ya da iki harflik adı seçilir. Alıcı bir değer (d Dikdortgen) ya da pointer (d *Dikdortgen) olabilir.

allocation

Programın heap'ten yeni bellek istemesi. Çok olursa çöp toplayıcıyı yorar ve yavaşlatır.

make, new, &T{}, string birleştirme, append ile büyüme ve interface'e kutulama heap ayırması yaratabilir (derleyici kaçış analiziyle kimini stack'te tutar). Her ayırma hem zaman alır hem çöp toplayıcıya iş çıkarır. go test -benchmem çıktısındaki allocs/op ve testing.AllocsPerRun bu sayıyı verir. Sayı Go sürümüne ve derleyici iyileştirmelerine göre biraz değişebilir; bu yüzden tam sayıdan çok azaldı mı diye bakılır.

n := testing.AllocsPerRun(10, func() {
    sink = make([]int, 100)
})
fmt.Println(n) // 1

alt test

`t.Run(ad, f)` ile başlatılan, adı olan ve tek başına çalıştırılabilen test parçası.

Tablo testinde her vakayı t.Run(tc.ad, func(t *testing.T) {...}) içine alırsan her vaka adlandırılmış bir alt test olur. Çıktıda TestKategori/on_iki gibi görünür (boşluklar _ olur) ve go test -run 'TestKategori/on_iki' yalnızca onu çalıştırır. Alt testte Fatalf yalnızca o alt testi bitirir.

for _, tc := range vakalar {
    t.Run(tc.ad, func(t *testing.T) {
        if got := Kategori(tc.yas); got != tc.want {
            t.Errorf("Kategori(%d) = %q, istenen %q", tc.yas, got, tc.want)
        }
    })
}

any

Hiç metot istemeyen interface (`interface{}`). Her tipin değeri içine girer.

any ile değişken, slice ya da map eleman tipi tanımlayabilirsin; fmt.Println gibi fonksiyonlar bu sayede her şeyi alır. Bedeli: derleyici içindeki tipi bilmez, x + 1 gibi işlemler önce tip doğrulaması ister. Gerçekten gerekmedikçe somut tip ya da küçük interface tercih edilir.

var x any = 42
x = "metin"
karisik := []any{1, "iki", true}

atomic

Tek bir sayıyı kilitsiz, bölünmez işlemlerle güncelleyen `sync/atomic` paketi: Add, Load, Store.

var n atomic.Int64 tanımlanır; n.Add(1), n.Load(), n.Store(v) ile kullanılır. İşlemler yarım kalmaz, araya başka goroutine giremez. Yalnız tek değer için uygundur; birden çok alanı birlikte tutarlı değiştirmek için Mutex gerekir.

var istek atomic.Int64
istek.Add(1)
fmt.Println(istek.Load())

bağlı liste

Her düğümün değeri ve sonraki düğüme pointer'ı taşıdığı zincir yapı. Son düğümün işaretçisi `nil`.

Slice'tan farklı olarak elemanlar art arda durmaz; her düğüm bir sonrakini gösterir. Başa ekleme ucuzdur (yalnızca pointer değişir); n'inci elemana gitmek için zinciri gezmek gerekir. Pointer alıştırması için ideal bir veri yapısıdır.

type Dugum struct {
	Deger   int
	Sonraki *Dugum
}

benchmark

`BenchmarkXxx(b *testing.B)` fonksiyonu: işi `b.N` kez çalıştırıp bir çağrının ortalama süresini ölçer.

Test dosyasında func BenchmarkXxx(b *testing.B) olarak yazılır ve go test -bench=. -benchmem ile çalışır. b.N'i Go seçer (süre ~1 sn olana kadar artırır). Çıktıda ns/op bir çağrının süresi, B/op ve allocs/op çağrı başına bellek ayırmadır. Hazırlık kodundan sonra b.ResetTimer() çağrılır. Çıktısı süreye bağlı olduğundan sonuçlar makineden makineye değişir.

func BenchmarkToplam(b *testing.B) {
    sayilar := make([]int, 1000)
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        Toplam(sayilar)
    }
}

BFS

Bir graf ya da sayfa ağını katman katman gezme: önce başlangıca bir adım uzaktakiler, sonra iki adım uzaktakiler.

Bir kuyruk tutarsın: başlangıcı ekle, kuyruğun başından bir öğe al, komşularını (sayfadaki linkler) sona ekle. Daha önce görülenler bir kümeye yazılır, böylece döngü yapan linkler (a→b→a) sonsuz işe yol açmaz. Çıktı sırası kuyruk sırasıdır, yani deterministiktir.

kuyruk := []string{baslangic}
for len(kuyruk) > 0 {
    url := kuyruk[0]
    kuyruk = kuyruk[1:]
    // url'yi işle, yeni linkleri kuyruğa ekle
}

birim testi

Bir fonksiyonu tek başına deneyen küçük test: `_test.go` dosyasında `TestXxx(t *testing.T)`.

Go'da test, testing paketi ve go test komutuyla çalışır. Test dosyası _test.go ile biter, test fonksiyonu Test ile başlar ve *testing.T alır. Sonucu if ile karşılaştırıp tutmuyorsa t.Errorf (devam eder) ya da t.Fatalf (testi bitirir) çağırırsın. Assert fonksiyonu yoktur.

func TestTopla(t *testing.T) {
    if got := Topla(2, 3); got != 5 {
        t.Errorf("Topla(2, 3) = %d, istenen 5", got)
    }
}

boş tanımlayıcı

`_` işareti: istemediğin değeri atmak için. Go kullanılmayan değişkene izin vermez.

_ bir değişken değildir; atamanın sağ tarafındaki değeri çöpe atar. q, _ := bol(7, 2) kalanı yok sayar. Kullanılmayan değişken hatasından kurtarmanın temiz yoludur.

q, _ := bol(7, 2)

byte

0-255 arası küçük sayı (`uint8`). Ham veri ve string'in alt birimi.

Bir string aslında bayt dizisidir. len(s) harf değil bayt sayısını verir; Türkçe harflerin çoğu UTF-8'de 2 bayt tutar.

closure

Kendi dışındaki değişkenleri yakalayıp kullanan anonim fonksiyon.

func() { ... } biçiminde yazılan ve tanımlandığı yerdeki değişkenleri görebilen fonksiyon. Goroutine başlatırken çok kullanılır: go func() { sonuc[i] = i * 10 }(). Go 1.22'den beri for döngü değişkeni her turda yenidir, bu yüzden her closure kendi turunun i'sini yakalar.

toplam := 0
ekle := func(n int) { toplam += n } // toplam'ı yakalar
ekle(5)
fmt.Println(toplam) // 5

comma-ok

`v, ok := m[k]` kalıbı: `ok` anahtarın gerçekten var olup olmadığını söyler.

Olmayan anahtar sıfır değer verdiği için 0 ile 'yok' ayırt edilemez. İkinci değer ok anahtar varsa true, yoksa false olur. Aynı kalıp tip doğrulama ve kanal okumada da çıkacak.

v, ok := puan["Zeynep"]
if !ok {
	fmt.Println("kayıt yok")
}

comparable

`==` ve `!=` ile karşılaştırılabilen tipler için yerleşik kısıt. Map anahtarı da bu tiplerden olmalıdır.

any kısıtıyla a == b yazamazsın, çünkü her tip karşılaştırılamaz (slice ve map karşılaştırılamaz). [T comparable] yazarsan == kullanabilir ve map[T]... oluşturabilirsin.

func Icerir[T comparable](s []T, x T) bool {
    for _, v := range s {
        if v == x {
            return true
        }
    }
    return false
}

context

İptal sinyalini, zaman aşımını ve istek verisini çağrı zinciri boyunca taşıyan standart yapı.

Fonksiyonların ilk parametresi olur (ctx). context.WithCancel elle iptal, context.WithTimeout süreyle iptal sağlar; cancel'ı defer cancel() ile çağır. İş ctx.Done() kanalını dinler; iptalde ctx.Err() context.Canceled ya da context.DeadlineExceeded verir. Üst context iptal edilince ondan türeyen tüm context'ler iptal olur.

ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
defer cancel()

çöp toplayıcı

Artık hiçbir pointer'ın göstermediği heap verisini kendiliğinden temizleyen çalışma zamanı parçası.

Go'da belleği elle serbest bırakmazsın. Çöp toplayıcı ulaşılamayan verileri bulup temizler. Bunun küçük bir çalışma maliyeti vardır; gereksiz heap tahsisinden kaçınmak bu yükü azaltır.

deadlock

Tüm goroutine'lerin birbirini beklediği, hiçbirinin ilerleyemediği durum.

Go çalışma zamanı tüm goroutine'lerin uykuda olduğunu fark ederse fatal error: all goroutines are asleep - deadlock! ile programı sonlandırır. Tipik nedenler: alıcısı olmayan tamponsuz gönderme, göndericisi olmayan alma, Done unutulmuş WaitGroup.

defer

Bir çağrıyı fonksiyon bitene kadar erteler. Temizlik işleri için.

defer f() çağrıyı fonksiyon dönerken çalıştırır. Argümanlar defer satırı çalışırken hesaplanır. Dosya kapatma, kilit bırakma gibi işlerde kullanılır.

f, _ := os.Open("a.txt")
defer f.Close()

değer semantiği

Go'da atamada ve fonksiyon çağrısında değerin kopyalanması. Kopyalanan şey tipe göre değişir.

int, string, struct ve dizi atanınca verinin kendisi kopyalanır. map, slice ve pointer kopyalanınca aynı verinin tutamacı kopyalanır; bu yüzden kopya aynı veriyi değiştirebilir. Bu fark 'Neden bu fonksiyon değeri değiştirdi?' sorusunun cevabıdır.

değişken

Bir değere verdiğin isim. `var` ya da `:=` ile tanımlanır.

Değeri saklayıp sonra adıyla kullanmanı sağlar. Uzun biçim var ad tip = değer, kısa biçim ad := değer. Kısa biçim yalnızca fonksiyon içinde çalışır.

var yas int = 7
puan := 10

dereference

Pointer'ın gösterdiği değere `*p` ile ulaşmak.

*p pointer'ı 'çözer': adresin tuttuğu değeri okur ya da *p = ... ile yazar. Struct pointer'ında alana erişirken k.Ad yazman yeter; Go (*k).Ad'ı senin yerine çözer. nil pointer'da dereference panic verir.

p := &x
fmt.Println(*p)

derleyici

Go kodunu çalıştırılabilir programa çeviren ve hataları program çalışmadan bulan araç.

Go derleyicisi tipleri, kullanılmayan değişken/import'ları ve söz dizimini kontrol eder. Hata mesajı dosya:satır:sütun: ne oldu biçimindedir.

dışa aktarma

Büyük harfle başlayan bir ismin paket dışından erişilebilir olması.

Go'da erişim kontrolü ismin ilk harfiyle yapılır: Hesapla, Kisi, Pi dışa açıktır; hesapla, kisi yalnızca kendi paketinde görünür. Kural fonksiyon, tip, sabit, değişken, struct alanı ve metotlar için geçerlidir.

func Topla(a, b int) int { return a + b } // dışa açık
func yuvarla(x float64) float64 { return x } // paket içi

dizi

Sabit sayıda, aynı tipte elemanı art arda tutan yapı: `[3]int`. Boyu sonradan değişmez.

Dizinin boyu tipinin parçasıdır: [3]int ile [4]int farklı tiplerdir. Dizi bir değerdir; atanınca ya da fonksiyona geçince tamamı kopyalanır. Günlük Go kodunda esnek kardeşi olan slice çok daha sık kullanılır.

var a [3]int            // [0 0 0]
b := [3]int{10, 20, 30}

doküman yorumu

Dışa açık bir ismin hemen üstüne yazılan, ismin adıyla başlayan `//` yorumu.

go doc ve pkg.go.dev bu yorumları otomatik gösterir. Paket yorumu package satırının üstünde // Package ad ... ile başlar. Yorum ile isim arasında boş satır olmamalıdır.

// Topla iki tam sayının toplamını döndürür.
func Topla(a, b int) int { return a + b }

done kanalı

Kapatılınca 'dur' sinyali veren kanal. Kapatma tüm dinleyicilere aynı anda ulaşır.

Kapalı bir kanaldan alma hemen döner, bu yüzden close(done) o kanalı select içinde dinleyen bütün goroutine'leri uyandırır. Genelde chan struct{} kullanılır; struct{} hiç yer kaplamaz çünkü değer taşımak değil sinyal vermek amaçtır.

done := make(chan struct{})
// ...
close(done) // herkese: dur

Duration

`time` paketinde süreyi taşıyan tip (içeride nanosaniye): `90 * time.Minute`.

Sabitlerle yazılır: time.Millisecond, time.Second, time.Minute, time.Hour. Seconds(), Minutes(), Hours() ondalıklı değer verir. Yazdırınca 1h30m0s gibi görünür. İki zaman arasındaki fark a.Sub(b) ile bir Duration olarak gelir.

d := 90 * time.Minute
fmt.Println(d)         // 1h30m0s
fmt.Println(d.Hours()) // 1.5

durum kodu

Yanıtın sonucunu söyleyen sayı: 2xx başarı, 4xx istemci hatası, 5xx sunucu hatası.

En çok kullanılanlar: 200 OK, 201 Created (yeni kaynak), 204 No Content (başarı, gövde yok), 400 bozuk istek, 401 kimlik yok, 403 yetki yok, 404 bulunamadı, 405 yöntem uygun değil, 409 çakışma, 422 geçersiz içerik, 500 sunucu hatası. Go'da http.StatusNotFound gibi sabitler kullanılır. İstemci önce koda bakar; bu yüzden doğru kodu seçmek önemlidir.

http.Error(w, "bulunamadi", http.StatusNotFound) // 404

düğüm

Bağlı listenin bir halkası: bir değer ve sonraki düğüme pointer.

Listede dolaşırken d := l.bas ile başlar, d = d.Sonraki ile ilerler, d == nil olunca durursun.

error

Başarısız olabilen işlemin döndürdüğü hata değeri. Başarılıysa `nil`.

Go'da hatalar özel mekanizma değil, dönüş değeridir. Başarısız olabilen fonksiyon sonucun yanında bir error döndürür. Kullanan taraf if err != nil ile kontrol eder.

n, err := strconv.Atoi("12a")
if err != nil {
	// hata var
}

errors.As

Hata zincirinde belirli bir hata tipini arar ve bulursa değişkenine yazar.

var h DogrulamaHatasi; errors.As(err, &h): zincirde bu tipte bir hata varsa true döner ve h dolar. Hedef değişkenin adresi verilir; çünkü As sonucu senin değişkenine yazar.

var dh DogrulamaHatasi
if errors.As(err, &dh) {
	fmt.Println(dh.Alan)
}

errors.Is

Hata zincirinde belirli bir hata değerinin (ör. sentinel) olup olmadığına bakar.

errors.Is(err, ErrX) zinciri katman katman açar; herhangi bir katman ErrX'e eşitse true döner. Sarmalanmış hatalarda err == ErrX çalışmaz, errors.Is çalışır.

if errors.Is(err, ErrUrunYok) {
	fmt.Println("ürün yok")
}

etiket

Döngünün önüne yazılan `ad:`. `break ad` / `continue ad` ile o döngüye ulaşılır.

İç içe döngülerde break yalnızca en içtekini bitirir. Dış döngüye etiket verip break etiket yazarsan doğrudan o döngüden çıkarsın. Kullanılmayan etiket derleme hatasıdır.

dis:
for i := 0; i < 3; i++ {
	for j := 0; j < 3; j++ {
		if i*j == 2 {
			break dis
		}
	}
}

fallthrough

`switch`'te bir sonraki `case`'in gövdesine koşulsuz düşmek. Nadiren gerekir.

Go'da case bitince switch kendiliğinden biter. Sonrakine geçmek istersen fallthrough yazarsın; sonraki case'in koşulu bakılmadan gövdesi çalışır.

fan-in

Birden çok kanaldaki değerleri tek kanalda toplama.

Her kaynak kanal için bir goroutine, hepsi aynı çıktı kanalına yazar. Çıktıyı kapatmak için WaitGroup ile tüm goroutine'lerin bitmesini bekleyen ayrı bir goroutine kullanılır. Birleşik çıktının sırası belirsizdir.

go func() {
    wg.Wait()
    close(cikti)
}()

fan-out

Aynı girdi kanalını birden çok goroutine'in okuyup işi aralarında paylaşması.

Bir değeri kanaldan yalnız bir goroutine alır, bu yüzden aynı kanalı okuyan N işçi işi bölüşür. Yavaş bir pipeline aşamasını hızlandırmak için kopyalanır. İşin hangi işçiye düşeceği belirsizdir.

fonksiyon

`func` ile tanımlanan, çağırdıkça çalışan, parametre alıp değer döndürebilen kod parçası.

Bir işi yapan, adı olan kod bloğu. Parametre alabilir, bir ya da birden çok değer döndürebilir. Büyük harfle başlayan fonksiyon paket dışından da çağrılabilir.

func topla(a, b int) int {
	return a + b
}

format fiili

`Printf` biçim metnindeki % işaretli yer tutucu: %d, %s, %v, %.2f, %T.

%d tam sayı, %s string, %v varsayılan biçim, %.2f iki ondalık, %T tip, %q tırnaklı metin. Fiil, bağlı olduğu değerin tipine uymalıdır.

fmt.Printf("%s: %.2f TL\n", "Kalem", 12.5)

functional options

Yapıcı fonksiyona istediğin kadar isteğe bağlı ayarı fonksiyon olarak veren API deseni: `Yeni(Port(80))`.

Seçenek, nesneyi değiştiren bir fonksiyondur: type Secenek func(*Sunucu). Yapıcı ...Secenek alır; önce varsayılanları kurar, sonra seçenekleri sırayla uygular. Yararı: yeni ayar eklemek mevcut çağrıları bozmaz, varsayılanlar tek yerde durur, çağrı kendini açıklar. Alternatif olan Yeni(adres, port, zamanAsimi, gunluk) uzadıkça okunmaz hale gelir.

func Port(p int) Secenek {
    return func(s *Sunucu) { s.Port = p }
}
s := YeniSunucu(Port(9090), Gunluk(true))

generics

Aynı kodu birçok tip için yazabilmeni sağlayan özellik: tip, fonksiyona parametre olarak verilir.

Generics ile Ilk gibi bir fonksiyonu int için, string için ayrı ayrı yazmak yerine bir kez yazarsın. Derleyici her kullanımda gerçek tiple kontrol eder; yani any gibi tip güvenliğini kaybetmezsin. Go 1.18'den beri vardır.

func Ilk[T any](s []T) T {
    return s[0]
}

go vet

Derlenen ama büyük olasılıkla hatalı kodu yakalayan araç: yanlış `Printf` biçimi, kopyalanmış kilit gibi.

go vet ./... derleyicinin kabul ettiği ama şüpheli bulduğu kodları listeler: Printf biçimi ile argüman uyuşmazlığı, sync.Mutex'in değer olarak kopyalanması, ulaşılamayan kod, yanlış struct etiketi. go test zaten küçük bir vet alt kümesini çalıştırır. CI'da go vet çalıştırmak ucuz ve kazançlıdır.

fmt.Printf("%d", "metin") // vet: %d bir string alıyor

gofmt

Go kodunu tek bir standart biçime sokan araç. Girinti TAB'dır.

gofmt boşlukları, girintiyi ve hizalamayı otomatik düzenler. Çoğu editör kaydederken çalıştırır. Böylece tüm Go kodu aynı görünür ve biçim tartışması olmaz.

goroutine

Go çalışma zamanının yönettiği, çok ucuz eşzamanlı iş parçacığı. `go f()` ile başlatılır.

go anahtar kelimesiyle çağrılan fonksiyon ayrı bir goroutine'de çalışır; çağıran beklemeden devam eder. Binlercesini başlatmak sorun değildir. main fonksiyonu bittiğinde program kapanır ve henüz bitmemiş goroutine'ler yarıda kesilir.

go fmt.Println("ayrı iş parçacığında")

gömme

Bir struct'a başka bir tipi alan adı vermeden yazmak: alan ve metotları doğrudan erişilir olur.

type Calisan struct { Kisi; Maas int } yazınca Kisi'nin alanları ve metotları Calisan üzerinden de çağrılabilir: c.Ad, c.Tanit(). Bu kalıtım değil birleşimdir. Calisan aynı adda kendi metodunu tanımlarsa o öne geçer; gömülene c.Kisi.Tanit() ile yine ulaşılır.

type Calisan struct {
	Kisi
	Maas int
}

handler

`ServeHTTP(w, r)` metodu olan her şey: gelen isteği alıp yanıtı `ResponseWriter`'a yazar.

http.Handler tek metotlu bir interface'tir: ServeHTTP(w http.ResponseWriter, r *http.Request). http.HandlerFunc(f) imzası uyan bir fonksiyonu Handler'a çevirir. Durum tutmak istersen struct'a ServeHTTP yazarsın. Yönlendirici (ServeMux), middleware ve test sunucusu da Handler'dır.

func Merhaba(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "Merhaba!")
}
var h http.Handler = http.HandlerFunc(Merhaba)

heap

Fonksiyon bittikten sonra da yaşayan verinin durduğu bellek; artık kullanılmayınca çöp toplayıcı temizler.

Heap'teki veriyi kimse göstermiyorsa Go'nun çöp toplayıcısı (garbage collector) otomatik olarak temizler. Stack'ten biraz daha pahalıdır. Hangi değişkenin heap'e gideceğine derleyici kaçış analiziyle karar verir.

HTTP

Web'in istek-yanıt protokolü: istemci istek gönderir, sunucu durum koduyla yanıt verir.

İstek: yöntem (GET, POST, PUT, DELETE), yol ve sorgu (/merhaba?ad=Ali), başlıklar ve isteğe bağlı gövde. Yanıt: durum kodu (200, 404, 500), başlıklar ve gövde. Go'da net/http paketi hem sunucu hem istemci için standart kütüphanededir; gelen istek *http.Request, giden yanıt http.ResponseWriter ile temsil edilir.

GET /merhaba?ad=Ali HTTP/1.1
Accept-Language: tr

HTTP/1.1 200 OK
Content-Type: text/plain

Merhaba, Ali!

httptest

Handler'ı ağ olmadan denemek için sahte istek (`NewRequest`) ve yanıt kaydedici (`NewRecorder`) sunan paket.

httptest.NewRequest(yontem, adres, govde) hazır bir *http.Request üretir; httptest.NewRecorder() yanıtı hafızada tutan sahte bir ResponseWriter verir. h.ServeHTTP(rec, req) sonrası rec.Code (durum kodu), rec.Body.String() (gövde) ve rec.Result().Header okunur. httptest.NewServer ise gerçek bir loopback sunucusu başlatır.

rec := httptest.NewRecorder()
h.ServeHTTP(rec, httptest.NewRequest("GET", "/x", nil))
fmt.Println(rec.Code, rec.Body.String())

idiyomatik

Go topluluğunun alıştığı, 'Go böyle yazılır' denen doğal yazım biçimi.

Aynı işi çok yoldan yazabilirsin; idiyomatik kod, Go okuyucusunun beklediği yoldur: kısa isimler, Get öneki olmayan erişimciler, erken dönüş, if err != nil hemen ardından, hataları sarmalamak. Kaynaklar: Effective Go, Go Code Review Comments ve standart kütüphanenin kendisi. gofmt ve go vet bu alışkanlıkların bir kısmını otomatik denetler.

// idiyomatik değil
func (k *Kullanici) GetAd() string { return k.ad }
// idiyomatik
func (k *Kullanici) Ad() string { return k.ad }

import

Başka bir paketi dosyaya getirir. Kullanılmayan import derleme hatasıdır.

import "fmt" ile fmt paketinin fonksiyonlarını kullanabilirsin. Birden çok paket için parantezli biçim kullanılır. Go, import edilip kullanılmayan paketi hata sayar.

import (
	"fmt"
	"strings"
)

import yolu

`import` satırındaki tam paket yolu (`math/rand`). Kodda paket adı yolun son parçasıdır.

Standart kütüphane yolları konuya göre klasörlenir (unicode/utf8, encoding/json). Kendi modülündeki paketin yolu modül adı artı alt klasördür. Takma ad verebilirsin: str "strings"; _ yan etki için, . ön eksiz kullanım için (kaçınılır).

import (
    "fmt"
    "math/rand"        // rand.Intn(...)
    str "strings"      // str.ToUpper(...)
)

init fonksiyonu

Paket yüklenirken `main`'den önce kendiliğinden çalışan, parametresiz özel fonksiyon.

Sıra: paket değişkenlerinin ilk değerleri, init(), main(). Bir pakette birden çok init olabilir ve yazıldıkları sırayla çalışır. Elle çağrılamaz. Gizli çalıştığı için hazırlık işini açık bir Kur() fonksiyonuna koymak çoğu zaman daha iyidir.

func init() {
    ayarlar = map[string]string{"dil": "tr"}
}

interface

Metot adlarından oluşan sözleşme. Bu metotlara sahip her tip, interface'i sağlar.

type Hayvan interface { Ses() string } yalnızca Ses() string ister. Hangi struct olduğuna bakmaz. Bir interface tipindeki değişken, onu sağlayan herhangi bir değeri tutabilir ve yalnızca interface'in metotlarını çağırabilirsin. Metodu eksik tip atanırsa derleme hatası alırsın.

type Hayvan interface {
    Ses() string
}

interface gömme

Bir interface'in içine başka interface'lerin adını yazarak metotlarını birleştirmek.

type OkuYazici interface { Okuyucu; Yazici } iki interface'in tüm metotlarını ister. Küçük interface'leri ayrı ayrı yazıp birleştirmek tekrarı önler. Standart kütüphanede io.ReadWriter böyle tanımlanmıştır.

type OkuYazici interface {
    Okuyucu
    Yazici
}

io.Reader

Tek metotlu (`Read`) interface: dosya, ağ bağlantısı, `os.Stdin` ve `strings.NewReader` hepsi bir Reader'dır.

Read(p []byte) (n int, err error) metoduna sahip her tip io.Reader'dır. Fonksiyonu io.Reader alacak biçimde yazarsan veri kaynağından bağımsız çalışır ve testte strings.NewReader(metin) ile denenir. bufio.NewScanner(r), io.ReadAll(r) ve json.NewDecoder(r) Reader alır.

r := strings.NewReader("merhaba")
veri, _ := io.ReadAll(r)
fmt.Println(string(veri)) // merhaba

iota

Sabit grubunda 0'dan başlayıp her satırda 1 artan otomatik sayaç.

const ( ... ) bloğunda iota her satırda bir artar. Sıralı numaralar (günler, durumlar, yönler) üretmenin kısa yoludur.

const (
	Kucuk = iota // 0
	Orta         // 1
	Buyuk        // 2
)

isimli dönüş değeri

Dönüş tipine isim verilmesi: `(bolum, kalan int)`. Çıplak `return` bunları döndürür.

İsimli dönüş değerleri fonksiyonun başında sıfır değerle hazır değişkenlerdir. Çıplak return onları döndürür. Kısa fonksiyonlarda belge işi görür; uzun fonksiyonlarda açık return a, b daha okunaklıdır.

func bol(a, b int) (bolum, kalan int) {
	bolum = a / b
	kalan = a % b
	return
}

istek gövdesi

İstemcinin istekle gönderdiği veri (`POST`/`PUT`'ta çoğunlukla JSON); Go'da `r.Body` okuyucusu.

r.Body bir io.Reader'dır. JSON için json.NewDecoder(r.Body).Decode(&v) kullanılır. Gövde istemciden geldiği için güvenilmezdir: okunamayan JSON 400, geçerli ama anlamsız içerik 422 ile reddedilir. dec.DisallowUnknownFields() yazım hatalarını yakalar, http.MaxBytesReader boyutu sınırlar.

var k Kitap
if err := json.NewDecoder(r.Body).Decode(&k); err != nil {
    http.Error(w, "gecersiz json", http.StatusBadRequest)
    return
}

kaçış analizi

Derleyicinin bir değişkenin adresi fonksiyondan dışarı 'kaçıyor mu' diye bakıp yerini seçmesi.

Adresi return ya da başka yolla fonksiyondan çıkıyorsa değişken heap'e konur; çıkmıyorsa stack'te kalır. Sen karar vermezsin. Bu sayede Go'da yerel değişkenin adresini döndürmek güvenlidir. Kararları go build -gcflags=-m ile görebilirsin.

func YeniKisi() *Kisi {
	k := Kisi{Ad: "Ece"}
	return &k // k heap'e kaçar
}

kanal

Goroutine'ler arasında değer taşıyan, tip güvenli boru. `ch <- v` gönderir, `<-ch` alır.

make(chan int) ile oluşturulur. Tamponsuz kanalda gönderen, alan çıkana kadar; alan da gönderen gelene kadar bekler. Bu hem veri taşır hem iki goroutine'i senkronlar. "Belleği paylaşarak iletişim kurma, iletişim kurarak belleği paylaş."

ch := make(chan string)
go func() { ch <- "selam" }()
fmt.Println(<-ch)

kapasite

Slice'ın arkadaki depoya yeni depo ayırmadan kaç eleman sığdırabileceği. `cap(s)` ile okunur.

len(s) kaç eleman olduğunu, cap(s) yeni depo ayırmadan kaç elemana kadar büyüyebileceğini söyler. append kapasite dolunca daha büyük bir depo ayırır ve elemanları oraya taşır. make([]int, 0, 10) kapasiteyi baştan belirler.

s := make([]int, 0, 3)
fmt.Println(len(s), cap(s)) // 0 3

kapsam

Bir değişkenin görünür olduğu kod bölgesi. `if` başındaki tanım yalnızca o `if`'te yaşar.

Süslü parantezler ({ }) yeni kapsam açar. Değişken tanımlandığı bloğun dışında kullanılamaz. Dar kapsam, isim karışıklığını ve hataları azaltır.

kısıt

Tip parametresinin hangi tiplerle kullanılabileceğini söyleyen kural (`any`, `comparable`, kendi interface'in).

Kısıt, tip parametresinin hemen yanına yazılır: [T any], [T comparable]. Kısıt interface biçimindedir; metot ya da tip listesi (~int | ~float64) içerebilir. Fonksiyonun içinde T ile yalnızca kısıtın izin verdiği işlemler yapılabilir.

type Sayi interface {
    ~int | ~float64
}

koşul

`true` ya da `false` üreten ifade. Go'da koşul mutlaka `bool` olmalı.

if, for ve switch koşula göre karar verir. ==, !=, <, >, <=, >= karşılaştırmaları ve &&, ||, ! mantıksal işleçleri bool üretir. Sayı ya da string koşul olarak kullanılamaz.

kurucu fonksiyon

Go'da kurucu yoktur; gelenek: `NewXxx` adlı, hazır ve geçerli bir değer döndüren fonksiyon.

func NewKumbara(hedef int) *Kumbara gibi fonksiyonlar varsayılanları ayarlar, map ve slice alanlarını make ile kurar. Böylece nil map'e yazma gibi hatalar baştan engellenir. Dil kuralı değil, topluluk geleneğidir.

func NewSepet() *Sepet {
	return &Sepet{Fiyatlar: make(map[string]float64)}
}

LIFO

Son giren ilk çıkar: `defer`'ler sondan başa doğru çalışır.

Üst üste konan tabaklar gibi: en son konan ilk alınır. Aynı fonksiyondaki birden çok defer bu sırayla çalışır.

map

Anahtar → değer tablosu: `map[string]int`. Anahtarla hızlı arama yapar.

Anahtar tipi == ile karşılaştırılabilir olmalı (string, sayı, bool...; slice olamaz). Olmayan anahtar tipin sıfır değerini verir. nil map'e yazılırsa panic olur; make(map[K]V) ile kur. range ile gezerken sıra garanti değildir.

puan := map[string]int{"Ali": 80}
puan["Ece"] = 95

Marshal

Go değerini JSON metnine çevirme (`json.Marshal`); tersi Unmarshal.

b, err := json.Marshal(v) bayt dilimi ve hata döndürür. Yalnız dışa aktarılan (büyük harfli) alanlar yazılır; etiketlerle ad ve davranış belirlenir. Map anahtarları sıralı yazılır. nil slice null, boş slice [] olur. MarshalIndent(v, "", " ") girintili yazar. Bir tipe MarshalJSON() ([]byte, error) yazarak kendi biçimini verebilirsin.

b, _ := json.Marshal(map[string]int{"b": 2, "a": 1})
fmt.Println(string(b)) // {"a":1,"b":2}

metot

Bir tipe bağlı fonksiyon: `func (k Kisi) Tanit() string`. `k.Tanit()` diye çağrılır.

Metot, func ile ad arasına yazılan alıcı (receiver) sayesinde belli bir tipe bağlanır. Go'da sınıf yoktur; tipleri tanımlarsın, metotları ayrıca bağlarsın. Aynı pakette herhangi bir tipe (struct, type Sayi int gibi) metot eklenebilir.

func (d Dikdortgen) Alan() float64 {
	return d.Genislik * d.Yukseklik
}

metot kümesi

Bir tipin çağırabileceği metotların kümesi. `T` yalnızca değer alıcılıları, `*T` ikisini de içerir.

T tipinin metot kümesinde sadece değer alıcılı metotlar vardır; *T'nin kümesinde hem değer hem pointer alıcılı metotlar bulunur. Bu kural Bölüm 7'de interface'leri karşılamada önemlidir.

middleware

Bir handler'ı sarıp önce/sonra iş yapan veya isteği durduran `func(http.Handler) http.Handler`.

Yetki, log, süre ölçümü, panic yakalama gibi her istekte tekrar eden işleri handler'dan ayırır. Biçimi: func(next http.Handler) http.Handler; içinde next.ServeHTTP(w, r) isteği içeri geçirir. next çağrılmazsa istek orada biter (kısa devre, örneğin 401). Zincirde en dıştaki middleware isteği ilk görür: A(B(h)) sırası A giriş, B giriş, h, B çıkış, A çıkış.

func Yetki(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if r.Header.Get("Authorization") != "Bearer gizli" {
            http.Error(w, "yetkisiz", http.StatusUnauthorized)
            return
        }
        next.ServeHTTP(w, r)
    })
}

modül

`go.mod` dosyası olan proje kökü ve altındaki tüm paketler. Adı import yollarının başıdır.

go mod init <ad> yeni bir modül başlatır. go.mod dosyasında module (ad), go (sürüm) ve require (bağımlılıklar) satırları bulunur. go.sum bağımlılıkların doğrulama özetini tutar. go mod tidy require listesini kullanılan paketlere göre düzeltir.

module github.com/ali/selam

go 1.22

require github.com/google/uuid v1.6.0

Mutex

Ortak veriye aynı anda tek goroutine'in girmesini sağlayan kilit (`sync.Mutex`): Lock ve Unlock.

mu.Lock() kilidi alır, başkası tutuyorsa bekler; mu.Unlock() bırakır. Arada kalan kod kritik bölgedir, içeride tek goroutine olur. Kilidi defer mu.Unlock() ile bırakmak alışkanlık olmalı. Mutex kopyalanmaz: onu taşıyan struct'ın metotları pointer alıcı olmalı. Yeniden girilebilir değildir: aynı goroutine iki kez Lock çağırırsa kendini kilitler.

mu.Lock()
defer mu.Unlock()
sayac++

nil

'Hiçbir şey' değeri. `error` için `nil` 'hata yok' demektir.

nil, pointer, slice, map, interface ve error gibi tiplerin sıfır değeridir. err == nil işlem başarılı demektir; err != nil hata var demektir.

ön ayırma

Büyüklüğü baştan biliniyorsa slice ya da map için yeri önceden ayırmak: `make([]T, 0, n)`.

append kapasite dolunca yeni ve daha büyük bir dizi ayırıp eskisini kopyalar; sonuç büyüdükçe bu tekrar eder. Eleman sayısını (ya da üst sınırını) biliyorsan make([]T, 0, n) ile tek seferde ayır; append sonra hiç yeniden ayırmaz. Aynı fikir map için make(map[K]V, n), string için Builder.Grow(n). Gereğinden büyük ayırmak bellek israfıdır, dengeli ol.

sonuc := make([]int, 0, len(girdi))
for _, v := range girdi {
    sonuc = append(sonuc, v*v)
}

örnek test

`ExampleXxx()` fonksiyonu: çıktısı `// Output:` yorumuyla karşılaştırılan, hem test hem doküman olan örnek.

Test dosyasında func ExampleKisalt() { fmt.Println(...); // Output: ... } yazılır. go test onu çalıştırır, standart çıktıyı // Output: altındaki satırlarla birebir karşılaştırır; pkg.go.dev dokümanında da örnek olarak görünür. Ad kuralı: ExampleFonksiyon, ExampleTip_Metot, ExampleFonksiyon_ek. Sıra belirsizse // Unordered output: kullanılır. Output: yorumu yoksa yalnızca derlenir.

func ExampleKisalt() {
    fmt.Println(Kisalt("merhaba dunya", 7))
    // Output: merhaba...
}

örtük uygulama

Tipin bir interface'i `implements` yazmadan, yalnızca gereken metotlara sahip olarak sağlaması.

Go'da bir tipin hangi interface'leri sağladığı tanımına yazılmaz. Metot adı ve imzası interface'tekiyle birebir tutuyorsa tip onu otomatik sağlar. Bu sayede interface'i tipten sonra, hatta başka pakette tanımlayabilirsin.

type Kopek struct{}
func (Kopek) Ses() string { return "Hav" }
// Kopek artık Hayvan'dır

özel hata tipi

`Error() string` metodu olan, mesajın yanında veri (alan adı, kod...) taşıyan kendi hata struct'ın.

Standart error yalnızca bir metin taşır. Çağıranın hata hakkında veri alması gerekiyorsa kendi tipini yazarsın ve errors.As ile yakalarsın. Ekstra veri gerekmiyorsa errors.New / fmt.Errorf yeter.

type DogrulamaHatasi struct{ Alan, Sebep string }
func (e DogrulamaHatasi) Error() string { return e.Alan + \": \" + e.Sebep }

paket

Birbiriyle ilgili kodun bir arada durduğu birim. Her Go dosyası bir paketle başlar.

Go kodu paketlere ayrılır. Dosyanın ilk satırı package ad olur. Çalıştırılabilir program için paket adı main, içinde de func main() bulunmalıdır. Hazır paketler (fmt, strings, os...) standart kütüphanededir.

package main

panic

Programın o noktada acil durdurulması. Önce `defer`'ler çalışır, sonra program mesajla biter.

panic("mesaj") ile ya da nil pointer erişimi, sınır dışı indeks, nil map'e yazma, tam sayıyı sıfıra bölme gibi hatalarda çalışma zamanı tarafından tetiklenir. Normal ve beklenen hatalar için error döndürülür; panic programcı hataları ve 'olamaz' durumları içindir. defer içindeki recover panic'i yakalayabilir.

var p *int
fmt.Println(*p) // panic: nil pointer dereference

parametre

Fonksiyona dışarıdan verilen girdi. Ad önce, tip sonra yazılır.

Tanım tarafında yazılana parametre, çağırırken verilen değere argüman denir. Aynı tipteki parametreler birleştirilebilir: a, b int.

pipeline

Kanal alıp kanal döndüren aşamaların zincirlenmesi; aşamalar aynı anda çalışır.

Her aşama bir goroutine'dir: girdi kanalını range ile okur, işler, çıktı kanalına gönderir ve girdi bitince çıktıyı defer close ile kapatır. Aşamalar iç içe çağrılarak bağlanır: Topla(Kare(Uret(...))). Tüketici erken bırakırsa aşamalar takılır; iptal için done kanalı ya da context gerekir.

for v := range Kare(Uret([]int{1, 2, 3})) {
    fmt.Println(v)
}

pointer

Bir değişkenin değerini değil adresini tutan değer. Tipi `*T`; `&x` ile alınır, `*p` ile içine gidilir.

p := &x ile x'in adresini taşıyan bir pointer elde edersin (tipi *int). *p gösterdiği değeri okur ve değiştirir; böylece fonksiyonlar çağıranın değişkenini güncelleyebilir. Değer verilmeyen pointer nil'dir; nil pointer'ın içine gitmek panic'tir.

x := 10
p := &x
*p = 25
fmt.Println(x) // 25

pointer alıcı

`func (s *Sayac) Artir()`: metot, değerin kopyası yerine kendisi üzerinde çalışır.

Değer alıcıda (s Sayac) metot kopya üzerinde çalışır; yapılan değişiklik kaybolur. Pointer alıcıda (s *Sayac) metot gerçek değere erişir ve onu değiştirebilir. Büyük struct'larda kopyalamaktan da kaçınır. Pointer'ların ayrıntısı Bölüm 5'te.

func (s *Sayac) Artir() {
	s.n++
}

polimorfizm

Aynı kodun farklı tiplerle çalışması. Go'da interface parametreleriyle sağlanır.

func Yaz(s Sekil) fonksiyonu Sekil'i sağlayan her tiple çalışır. Hangi tipin metodunun çağrılacağı çalışırken belirlenir. Yeni bir tip eklemek için mevcut fonksiyonları değiştirmen gerekmez.

sekiller := []Sekil{Kare{2}, Daire{1}}
for _, s := range sekiller {
    fmt.Println(s.Alan())
}

profilleme

Programın zamanı ve belleği hangi fonksiyonlarda harcadığını örnekleyerek bulma; Go'da `pprof` ile yapılır.

Benchmark 'yavaş' der, profil 'nerede yavaş' der. go test -cpuprofile cpu.out (CPU) ya da -memprofile mem.out (bellek) ile profil alınır, go tool pprof cpu.out içinde top en pahalı fonksiyonları, list Ad satır satır dökümü gösterir. Servislerde net/http/pprof paketi canlı /debug/pprof sayfası açar. Profil örnekleme yaptığı için çok kısa fonksiyonlarda yaklaşık sonuç verir.

go test -bench=. -cpuprofile cpu.out
go tool pprof cpu.out
(pprof) top

range

`for ... := range x` ile string, sayı ya da koleksiyon üzerinde dönme.

String üzerinde her tur konum (bayt) ve rune verir. Sayı üzerinde (range 5, Go 1.22+) 0'dan n-1'e kadar sayar. İstemediğin değeri _ ile atarsın.

for i, r := range "çay" {
	fmt.Println(i, string(r))
}

recover

`defer` içinde çağrılınca panic'i yakalayan yerleşik fonksiyon. Panic yoksa `nil` verir.

recover() yalnızca bir defer ile ertelenmiş fonksiyonun içinde işe yarar. Panic'in değerini döndürür ve program çökmesini durdurur. İsimli dönüş değeri kullanarak panic'i error'a çevirmek yaygın bir kalıptır.

defer func() {
	if r := recover(); r != nil {
		err = fmt.Errorf("kurtarıldı: %v", r)
	}
}()

referans tarih

Go'nun tarih biçim şablonu: `2006-01-02 15:04:05` (1 ay, 2 gün, 3 saat, 4 dk, 5 sn, 6 yıl).

Format ve Parse YYYY-MM-DD gibi harfler değil, tam şu sayıları kullanır: 2006 yıl, 01 ay, 02 gün, 15 saat, 04 dakika, 05 saniye. Şablonu bu sayılarla kendin yazarsın: t.Format("02.01.2006") → 15.03.2024.

t := time.Date(2024, time.March, 15, 14, 30, 0, 0, time.UTC)
fmt.Println(t.Format("2006-01-02")) // 2024-03-15

REST

Kaynakları adresler (`/kitaplar/5`) ve HTTP yöntemleriyle (`GET`, `POST`, `DELETE`) yöneten API tasarım biçimi.

REST API'de her varlık bir adrestir ve yöntem eylemi söyler: GET /kitaplar listeler, GET /kitaplar/5 getirir, POST /kitaplar oluşturur (201), DELETE /kitaplar/5 siler (204). Veri genelde JSON'dur; hata ve başarı durum koduyla anlatılır, hatalar tutarlı bir JSON biçimi taşır.

GET    /kitaplar        -> 200 [...]
POST   /kitaplar        -> 201 {...}
DELETE /kitaplar/5      -> 204

rune

Tek bir Unicode karakter. Aslında `int32`; tek tırnakla yazılır: 'ç'.

rune, bir karakterin Unicode numarasını tutan tam sayıdır. 'A' bir rune'dur (65). string üzerinde range ile gezince her harf rune olarak gelir. Çift tırnak ise string demektir.

var harf rune = 'ç'

RWMutex

Birçok okuyucuya aynı anda, yazıcıya tek başına izin veren kilit: RLock/RUnlock ve Lock/Unlock.

Okuma çok, yazma az olan verilerde (önbellek, ayarlar) Mutex'ten verimlidir. RLock altında birden çok goroutine aynı anda okuyabilir; Lock alan yazıcı herkes çıkana kadar bekler ve tek başına girer. RLock altında veriyi değiştirmek veri yarışıdır.

c.mu.RLock()
defer c.mu.RUnlock()
v, ok := c.veri[k]

sabit

`const` ile tanımlanan, sonradan değişmeyen değer.

Sabitlere yeni değer atanamaz. Tipsiz sabitler (const k = 5) hem int hem float64 yerine kullanılabilecek kadar esnektir.

const pi = 3.14159

sahte bağımlılık

Testte gerçek bağımlılığın (e-posta, veritabanı, saat) yerine geçen, çağrıları kaydeden basit tip.

Kod bağımlılığını küçük bir interface olarak ister; üretimde gerçek tip, testte sahte (fake) tip verilir. Sahte tip genelde küçük bir struct'tır: çağrıları bir dilime kaydeder ve bir alan doluysa hata döndürür. Test o kayda bakarak doğrular; zor üretilen hata durumları da kolayca denenir. Go'da çoğu zaman mock kütüphanesine gerek kalmaz.

type SahteGonderici struct {
    Gidenler []string
    Hata     error
}
func (s *SahteGonderici) Gonder(kime, metin string) error {
    if s.Hata != nil { return s.Hata }
    s.Gidenler = append(s.Gidenler, kime+": "+metin)
    return nil
}

sarmalama

Bir hatayı bağlam ekleyerek `fmt.Errorf("...: %w", err)` ile yeni bir hatanın içine koymak.

Sarmalanan hata zincirde saklı kalır: errors.Unwrap bir katman açar, errors.Is ve errors.As tüm zinciri tarar. %v kullanırsan yalnızca metin kopyalanır ve orijinal hata kaybolur.

return fmt.Errorf("elma siparişi: %w", err)

sayaç

Döngüde kaçıncı turda olduğunu tutan değişken (genelde `i`).

Klasik for i := 0; i < n; i++ döngüsünde i sayaçtır. Kapsamı döngüyle sınırlıdır.

scanner

`bufio.NewScanner` ile kurulan, girdiyi satır satır okuyan araç.

Scan() bir sonraki satırı okur ve satır varsa true döner. Text() okunan satırı string olarak verir. Girdi bitince Scan() false döner.

scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
	fmt.Println(scanner.Text())
}

select

Birden çok kanal işleminden hazır olanı seçen deyim. `default` varsa hiçbiri hazır değilken beklemeden çalışır.

select switch'e benzer ama her case bir kanal işlemidir. Birden fazlası hazırsa rastgele biri seçilir; hiçbiri hazır değilse (ve default yoksa) bekler. default ile bloklamayan gönderme/alma yazılır.

select {
case v := <-a:
    fmt.Println("a:", v)
case v := <-b:
    fmt.Println("b:", v)
}

semantik sürümleme

`vANA.ALT.YAMA` biçiminde sürüm numarası: ana uyumsuz, alt yenilik, yama hata düzeltmesi.

Ana sürüm artarsa kodun bozulabilir. Alt ve yama artışlarının geriye uyumlu olması beklenir. v0.x sürümleri kararsızdır. Ana sürüm 2 ve üstünde modül yoluna /v2 eklenir. go get [email protected] belirli sürümü getirir.

go get github.com/google/[email protected]

sentinel hata

Paket düzeyinde `var ErrX = errors.New(...)` ile tanımlanan, çağıranın karşılaştırabileceği hazır hata değeri.

Adlandırma geleneği Err öneki (io.EOF gibi bazı eski istisnalar hariç). Sarmalama varsa == yerine errors.Is ile kontrol edilir. Dışarı açılan her sentinel paketin API'sinin parçası olur; ihtiyaç kadar kullan.

var ErrUrunYok = errors.New("ürün bulunamadı")

ServeMux

Gelen isteği yol kalıbına (`"GET /kitap/{id}"`) göre doğru handler'a yönlendiren HTTP yönlendirici.

http.NewServeMux() ile oluşturulur, mux.HandleFunc(kalıp, f) ile kalıp eklenir. Kalıp başında yöntem olabilir (GET /...); {id} joker parçadır ve r.PathValue("id") ile okunur; /{$} yalnızca kök yolu, {yol...} kalan yolu yakalar. Eşleşme yoksa otomatik 404, yöntem uymuyorsa otomatik 405 döner; birden çok kalıp uyuyorsa en özeli kazanır. Mux'ın kendisi bir Handler'dır. Go 1.22 ile geldi.

mux := http.NewServeMux()
mux.HandleFunc("GET /selam/{ad}", func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Selam, %s!", r.PathValue("ad"))
})

sıfır değer

Değer vermediğin değişkenin tipine göre aldığı başlangıç değeri: 0, "", false, nil.

Go'da her değişken tanımlanır tanımlanmaz kullanılabilir. Sayılar 0, string boş metin "", bool false, pointer/slice/map nil olur. Böylece tanımsız (garbage) değer yoktur.

var n int      // 0
var s string   // ""
var ok bool    // false

sınır değer

Bir koşulun davranışının değiştiği nokta ve hemen yanındaki değerler (örn. 89, 90 için `>= 90`).

Hatalar çoğunlukla eşitlik noktalarında olur: < ile <= karışır, bir eksik ya da bir fazla yazılır. Her eşik için eşiğin hemen altını ve tam kendisini test et (89/90), ayrıca geçerlilik uçlarını (-1, 0, 100, 101), boş girdiyi ve sıfır değeri.

{89, "BB"}, // eşiğin altı
{90, "AA"}, // tam eşik

slice

Uzunluğu değişebilen liste: `[]int`. Arkada bir dizinin bölümüne bakar.

Slice üç şey taşır: arka plan dizisine bir işaret, uzunluk (len) ve kapasite (cap). append ile büyür. Dilimleme ve atama veriyi kopyalamaz; aynı arka plan dizisini paylaşır. Bağımsız kopya için copy kullanılır. Değer verilmeyen slice nil'dir.

s := []int{3, 1, 4}
s = append(s, 1)

slice başlığı

Slice değişkeninin kendisi: arka plan dizisine pointer, `len` ve `cap` taşıyan küçük kayıt.

Slice'ı atamak ya da fonksiyona vermek yalnızca bu üçlü başlığı kopyalar; elemanlar paylaşılır. append yeni bir başlık döndürür (gerekirse yeni bir dizi de ayırır) ve eski başlığa dokunmaz. Bu yüzden append'in sonucunu atamak gerekir.

s := []int{1, 2, 3}
t := s // başlık kopyalandı, dizi paylaşıldı

sonsuz döngü

Koşulsuz `for { }`. `break` ya da `return` ile çıkılır.

Koşul yazılmazsa for hiç bitmez. Sunucularda, oyunlarda ve girdi beklerken kullanılır. Çıkış koşulunu unutmak programı sonsuza kadar döndürür.

for {
	if bitti {
		break
	}
}

stack

Fonksiyonun yerel değişkenlerinin durduğu hızlı bellek bölgesi; fonksiyon bitince boşalır.

Yerel değişkenlerin çoğu stack'te yaşar. Alan fonksiyon dönünce kendiliğinden geri alınır, bu yüzden çok ucuzdur. Adresi fonksiyondan dışarı çıkan değişkenler ise derleyici tarafından heap'e taşınır (kaçış analizi).

stdin

Programın girdi akışı. Klavyeden ya da yönlendirilmiş dosyadan gelir.

Go'da os.Stdin standart girdidir. Satır satır okumak için bufio.NewScanner(os.Stdin) kullanılır.

Stringer

`String() string` metodu olan tip; `fmt` yazdırırken bu metodu kullanır.

fmt.Stringer, tek metotlu bir interface'tir. Tipine String() yazarsan Println, %v ve %s senin metnini kullanır; slice ve map içindeki elemanlar için de. String() içinde kendi değerini %v ile yazdırma: sonsuz özyineleme olur.

func (n Nokta) String() string {
    return fmt.Sprintf("(%d, %d)", n.X, n.Y)
}

strings.Builder

Çok parçalı metni kopyalamadan biriktiren tampon: WriteString ile ekle, String ile al.

String değiştirilemez, bu yüzden döngüde s += x her turda yeni string kopyalar. var sb strings.Builder tek bir büyüyen tampona ekler: WriteString, WriteByte, WriteRune, sonuç için String(). fmt.Fprintf(&sb, ...) ile biçimli yazılır (&sb verilir). Builder kullanıldıktan sonra kopyalanmamalı; Grow(n) baştan yer ayırır.

var sb strings.Builder
sb.WriteString("Go")
sb.WriteByte('!')
fmt.Println(sb.String()) // Go!

struct

Farklı tipte alanları tek bir isim altında toplayan kendi tipin.

type Kisi struct { Ad string; Yas int } yeni bir tip tanımlar. Alanlara k.Ad ile erişilir. struct bir değerdir: atanınca ve fonksiyona geçince kopyalanır. Tüm alanları karşılaştırılabilirse struct'lar == ile karşılaştırılabilir.

type Kisi struct {
	Ad  string
	Yas int
}

struct etiketi

Alanın yanına yazılan `json:"ad"` gibi üst bilgi. Paketler (örn. encoding/json) bunu okur.

Etiket, alan tipinin ardından ters tırnak içinde yazılır. encoding/json alanın JSON'daki adını ve seçenekleri (omitempty, -) buradan okur. Derleyici içeriği denetlemez; yazım hatası sessizce yok sayılır.

type Kullanici struct {
	Ad string `json:"ad"`
}

sync.Once

Bir fonksiyonu kaç goroutine çağırırsa çağırsın tam bir kez çalıştıran yardımcı: `once.Do(f)`.

İlk Do çağrısı f'yi çalıştırır; eşzamanlı diğer çağıranlar f bitene kadar bekler, sonraki çağrılar hiçbir şey yapmaz. Bir Once yalnız bir fonksiyon çalıştırır; farklı işler için ayrı Once kullan. Tembel başlatma (lazy init) için idealdir.

var once sync.Once
once.Do(func() { fmt.Println("bir kez") })
once.Do(func() { fmt.Println("çalışmaz") })

sync.Pool

Sık kullanılıp atılan geçici nesneleri yeniden kullanmak için havuz: `Get` al, `Put` geri ver.

sync.Pool eşzamanlı güvenlidir. Get() havuzda nesne varsa onu, yoksa New fonksiyonuyla yenisini verir; işin bitince Put(x) ile iade edersin. Havuzdaki nesne önceki kullanımdan kalma olabilir, bu yüzden kullanmadan önce sıfırla (Reset). Çöp toplayıcı havuzu istediği zaman boşaltabilir: havuz bir önbellek olarak düşünülür, saklama yeri olarak değil. Önce ölç; küçük ve ucuz nesneler için havuz kazandırmaz.

var havuz = sync.Pool{New: func() any { return new(bytes.Buffer) }}
b := havuz.Get().(*bytes.Buffer)
b.Reset()
defer havuz.Put(b)

t.Helper

Fonksiyonu test yardımcısı olarak işaretler; hata satırı yardımcıyı çağıran satırı gösterir.

Yardımcı fonksiyonun ilk satırında t.Helper() çağırırsan test hatası yardımcının içindeki satırı değil, yardımcıyı çağıran test satırını gösterir. Yardımcı t testing.TB (ya da *testing.T) alır; içinde Fatalf çağrısı onu çağıran testi bitirir.

func esit(t testing.TB, got, want int) {
    t.Helper()
    if got != want {
        t.Errorf("got %d, istenen %d", got, want)
    }
}

tablo güdümlü test

Vakaları (girdi + beklenen sonuç) bir struct diliminde toplayıp tek döngüyle deneyen test kalıbı.

Go'da en yaygın test biçimidir. Her vaka bir struct, hepsi bir dilimde; döngü got ile want'ı karşılaştırır. Yeni vaka eklemek bir satırdır ve Errorf devam ettiği için tek çalıştırmada başarısız tüm vakalar görünür. Sıfır, negatif ve sınır değerleri tabloya eklemeyi unutma.

vakalar := []struct{ a, b, want int }{
    {1, 2, 3},
    {0, 0, 0},
}
for _, tc := range vakalar {
    if got := Topla(tc.a, tc.b); got != tc.want {
        t.Errorf("Topla(%d, %d) = %d, istenen %d", tc.a, tc.b, got, tc.want)
    }
}

tamponlu kanal

Belirli sayıda değeri bekletebilen kanal: `make(chan int, 3)`. Tampon dolana kadar gönderme bloklamaz.

Tampon boyutu cap(ch), içindeki değer sayısı len(ch) ile okunur. Tampon doluysa gönderen, boşsa alan bekler. Değerler giriş sırasıyla (FIFO) çıkar.

ch := make(chan int, 2)
ch <- 1
ch <- 2 // henüz bloklamaz

test kapsamı

Testlerin çalıştırdığı satırların toplam satırlara oranı: `go test -cover`.

go test -cover coverage: 71.4% of statements yazar; go test -coverprofile=c.out ve go tool cover -html=c.out kodu yeşil (çalıştı) ve kırmızı (hiç çalışmadı) olarak gösterir. Kapsam, hangi yolların hiç denenmediğini gösterir ama doğruluğu garanti etmez: %100 kapsamlı bir test seti sınır hatalarını kaçırabilir.

go test -cover
go test -coverprofile=c.out
go tool cover -html=c.out

tip

Değerin ne tür bir şey olduğu: int, float64, string, bool...

Go statik tiplidir: her değerin tipi derleme sırasında bilinir. Tipler birbirine kendiliğinden dönüşmez; dönüşümü açıkça float64(x) gibi yazarsın.

tip çıkarımı

Derleyicinin tip parametresini fonksiyona verilen argümanlardan kendisi bulması.

Ilk([]string{"a"}) yazdığında T'nin string olduğunu derleyici argümandan çıkarır; Ilk[string](...) yazmana gerek kalmaz. Tip yalnızca dönüş değerinden anlaşılıyorsa çıkarım yapılamaz; o zaman Sifir[int]() gibi açıkça yazarsın.

Ilk([]int{1, 2}) // T = int

tip doğrulama

Interface içindeki gerçek tipi çıkarmak: `n, ok := x.(int)`.

x.(T) tek değerle kullanılırsa tip tutmadığında panic eder. İki değerli biçim (v, ok := x.(T)) güvenlidir: tutmazsa ok false, v sıfır değerdir. T somut bir tip ya da başka bir interface olabilir.

var x any = "selam"
s, ok := x.(string) // "selam", true
n, ok := x.(int)    // 0, false

tip parametresi

Fonksiyon ya da tip adından sonra köşeli parantezle yazılan, 'bir tip gelecek' diyen yer tutucu (`[T any]`).

func Ilk[T any](s []T) T içindeki T, çağrıldığı anda gerçek tipe (int, string...) dönüşür. Yan yana birden fazla yazılabilir: [K comparable, V any]. Her tip parametresi bir kısıt ile birlikte gelir.

func Esle[K comparable, V any](k K, v V) map[K]V {
    return map[K]V{k: v}
}

tolerans

Ondalık sayıları tam eşitlik yerine `math.Abs(a-b) < 0.01` gibi küçük farkla karşılaştırma.

float64 hesaplarında küçük yuvarlama farkları oluşur (0.1+0.2 != 0.3). Bu yüzden iki ondalık sayının farkının mutlak değerinin küçük bir eşikten küçük olup olmadığına bakılır.

math.Abs(a-b) < 0.01

type switch

Bir interface değerini gerçek tipine göre dallandıran switch: `switch v := x.(type)`.

Her case bir tip adı alır ve o dalda v o tipe sahiptir. case a, b: ile birden çok tip yazarsan v interface tipinde kalır. case nil boş interface'i, default kalanları yakalar. x.(type) yazımı yalnızca switch içinde geçerlidir.

switch v := x.(type) {
case int:
    fmt.Println(v + 1)
case string:
    fmt.Println(len(v))
}

Unmarshal

JSON metnini Go değerine çözme (`json.Unmarshal(veri, &hedef)`); hedef pointer olmalı.

Fazladan JSON alanları yok sayılır, eksik alanlar sıfır değerde kalır; tip uyuşmazlığı ve bozuk JSON hata verir (*json.UnmarshalTypeError, *json.SyntaxError). Şekli bilinmeyen JSON any'ye çözülür; sayılar float64 olur. Reader'dan okumak ve bilinmeyen alanı yasaklamak için json.NewDecoder(r) ve DisallowUnknownFields() kullanılır.

var u struct{ Ad string `json:"ad"` }
err := json.Unmarshal([]byte(`{"ad":"Ali"}`), &u)

UTF-8

Go'nun metni tuttuğu kodlama. ASCII harfler 1, Türkçe harflerin çoğu 2 bayt tutar.

Go string'leri UTF-8 bayt dizisidir. len("çay") 4'tür çünkü ç 2 bayt. Harf sayısı için []rune(s) ya da utf8.RuneCountInString kullanılır.

veri yarışı

İki goroutine'in aynı değişkene eşzamanlı eriştiği ve en az birinin yazdığı, senkronizasyonsuz durum.

Sonuç öngörülemez: güncellemeler kaybolabilir, program bazen doğru bazen yanlış çalışır. sayac++ tek işlem gibi görünür ama oku-artır-yaz adımlarıdır; iki goroutine araya girerse biri kaybolur. Çözüm: erişimi kanalla tek goroutine'e toplamak ya da kilit (Mutex) kullanmak. go run -race yarışları bulur.

sayac := 0
for i := 0; i < 100; i++ {
    go func() { sayac++ }() // YARIŞ!
}

WaitGroup

Bir grup goroutine'in bitmesini beklemeye yarayan sayaç (`sync` paketi): Add, Done, Wait.

wg.Add(1) bir iş başladı, wg.Done() bir iş bitti, wg.Wait() sayaç sıfırlanana kadar bekler. Add goroutine başlamadan önce çağrılır. WaitGroup kopyalanmamalı; fonksiyona *sync.WaitGroup olarak verilir.

var wg sync.WaitGroup
wg.Add(1)
go func() {
    defer wg.Done()
    fmt.Println("iş")
}()
wg.Wait()

worker pool

Sabit sayıda işçi goroutine'in ortak bir iş kanalından çalıştığı, eşzamanlılığı sınırlayan desen.

İşler isler kanalına konur; N işçi for is := range isler ile alır ve sonuçları sonuclar kanalına ya da kendi hücrelerine yazar. close(isler) işçilere iş kalmadığını bildirir. Sonuçlar sırasız gelir; sırayı korumak için işe indeks eklenir.

for range 3 {
    wg.Add(1)
    go isci(isler, sonuclar, &wg)
}

yükseltilmiş

Gömülen tipin alan ve metotlarının, dıştaki tip üzerinden doğrudan çağrılabilmesi.

c.Ad aslında c.Kisi.Ad'dır; Go uzun yolu senin yerine yazar. Bu kısayola yükseltme denir.

zaman karmaşıklığı

Girdi büyüdükçe işlem sayısının nasıl büyüdüğü: O(n) doğrusal, O(n²) karesel.

İç içe iki döngü n eleman için yaklaşık n×n adım yapar: O(n²). 1000 elemanda 1 milyon, 200 binde 40 milyar adım. Çoğu zaman bellek ayırmayı azaltmaktan çok daha büyük kazanç algoritmayı değiştirmektedir: map ile aramayı O(n)'den O(1)'e indirmek, önek toplamları kullanmak gibi.

// O(n²): her eleman için listeyi baştan tara
// O(n):  map ile tek geçiş