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) // 1alt 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) // 5comma-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çidizi
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: durDuration
`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ıyorgofmt
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)) // merhabaiota
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"] = 95Marshal
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-15REST
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 -> 204rune
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şikslice
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 = inttip 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ş