Menu

Golang Closure'lar ve Anonim Fonksiyonlar, Örneklerle

Go'daki anonim fonksiyonlar çevrelerindeki kapsamdan değişkenleri yakalayabilir. Closure'lar böyle oluşur: kendi durumunu tutan sayaçlar, üreteçler, middleware'ler ve callback'ler.

Bu sayfada çalıştırılabilir editörler var - düzenle, çalıştır ve sonucu anında gör.

Anonim fonksiyonlar

Fonksiyon literal'i, adı olmayan bir fonksiyondur. Bir ifadedir, bu yüzden bir değerin gidebileceği her yere gidebilir: bir değişkene, bir argümana, bir dönüş değerine, bir struct alanına.

Go'da ok ya da lambda kısaltması yoktur. Tam func(params) results { body } biçimi bir tane yazmanın tek yoludur.

Değişken yakalamak

Bir fonksiyon literal'i, çevreleyen fonksiyonun değişkenlerini kullanabilir. Onları kopyalamaz: closure ve çevreleyen kod tek bir değişkeni paylaşır.

Yakalanan değişken, onu tanımlayan fonksiyon döndükten sonra bile, bir closure ona başvurduğu sürece yaşar. Derleyici gerektiğinde onu heap'e taşır; bunu hiçbir zaman kendiniz yönetmezsiniz.

Üreteçler ve sayaçlar

Bir fonksiyondan bir closure döndürmek, döndürülen her fonksiyona kendi özel durumunu verir.

counter()'a yapılan her çağrı yeni bir n oluşturur, bu yüzden c1 ve c2 bağımsız sayar. Dışarıdaki hiçbir şey n'i okuyamaz ya da sıfırlayamaz; bu da bunu küçük bir kapsülleme biçimi yapar.

Döngülerde closure'lar (Go 1.22 ve sonrası)

Go 1.22'den önce bir for döngüsünün tüm döngü için tek bir döngü değişkeni vardı. İçinde oluşturulan closure'ların hepsi o tek değişkeni yakalıyor ve son değerini görüyordu. Bu, Go'daki en yaygın closure hatasıydı.

Go 1.22'den beri her yineleme kendi kopyasını alır, bu yüzden aşağıdaki kod 0 1 2 yazdırır:

Go 1.21 ve öncesinde for i := 0; i < 3; i++ şeklinde yazılmış aynı döngü (tam sayı üzerinde range de 1.22'de gelen bir yeniliktir) 3 3 3 yazdırıyordu. Yeni kural, go.mod içindeki go satırına göre modül başına uygulanır; bu yüzden eski bir modül, go satırı 1.22 ya da sonrasına yükseltilene kadar eski davranışı alır. Eski kodda döngülerin içinde hâlâ i := i ya da v := v göreceksiniz: bu elle yapılan düzeltmeydi ve artık gereksizdir.

Yineleme başına kural yalnızca for ifadesinin kendisinin tanımladığı değişkenleri kapsar. Döngüden önce tanımlanıp gövdede güncellenen bir değişken hâlâ tek bir paylaşılan değişkendir:

var last string
for _, s := range items {
	last = s
	handlers = append(handlers, func() { use(last) }) // every closure sees the final value
}

Closure'lar ve goroutine'ler

Bir fonksiyon literal'iyle başlatılan goroutine de bir closure'dır. Aynı paylaşım kuralları geçerlidir, artı olağan eşzamanlılık kuralı: birden fazla goroutine yakalanan bir değişkene yazıyorsa bir mutex ya da channel gerekir.

Bu her zaman 5050 yazdırır. Her goroutine kendi i'sini okur (Go 1.22 döngü semantiği) ve paylaşılan total'a bir kilit altında yazar. Mutex'i kaldırın, sonuç öngörülemez hale gelir; go run -race bunu bir data race olarak raporlar. WaitGroup ve race detector için goroutine'ler sayfasına bakın.

Closure'ların gerçek kodda görüldüğü yerler

  • Sıralama ve arama: slices.SortFunc, slices.IndexFunc, sort.Slice hepsi bir fonksiyon literal'i alır.
  • Ertelenmiş temizlik: defer func() { ... }(), fonksiyondan çıkarken bir blok çalıştırır ve fonksiyonun değişkenlerini okuyabilir. defer sayfasına bakın.
  • HTTP middleware: bir handler alıp onu saran yeni bir http.HandlerFunc literal'i döndüren bir fonksiyon.
  • Yapılandırma: functional options (func WithTimeout(d time.Duration) Option { return func(c *Config) { c.Timeout = d } }), argüman üzerine kurulmuş closure'lardır.

Hem bir parametreyi hem de bir sayacı yakalayan bir middleware örneği:

Sık yapılan hatalar

  • Bir anlık görüntü beklemek. Bir closure değişkenin, closure oluşturulduğundaki değerini değil, çalıştığı andaki güncel değerini okur. Anlık görüntü gerekiyorsa değeri parametre olarak aktarın: go func(v int) { ... }(x).
  • Özyinelemeli literal'ler. Bir fonksiyon literal'i, aynı := içinde atandığı değişken üzerinden kendine başvuramaz. Önce değişkeni tanımlayın: var walk func(n int); walk = func(n int) { ... walk(n-1) }.
  • Goroutine'lerden senkronize olmayan yazmalar. Yakalamak senkronizasyon değildir. Paylaşılan yazmaları koruyun.

Sıkça Sorulan Sorular

Go'da anonim fonksiyon nedir?

Adı olmayan bir fonksiyon literal'idir: func(x int) int { return x * 2 }. Onu bir değişkene atayabilir, argüman olarak aktarabilir, döndürebilir ya da kapanış süslü parantezinden sonra () ekleyerek hemen çağırabilirsiniz. Go'da ayrı bir lambda söz dizimi yoktur; fonksiyon literal'i lambdanın kendisidir.

Go'da closure nedir?

Dışında tanımlanmış değişkenlere başvuran bir fonksiyon literal'idir. Fonksiyon bu değişkenleri canlı tutar ve çevreleyen kapsamla paylaşır, bu yüzden closure içinde yapılan değişiklikler dışarıda, dışarıda yapılanlar da içeride görünür. Değişkenler kopyalanmaz, referansla yakalanır.

Go closure'ları değişkenleri değerle mi yoksa referansla mı yakalar?

Referansla. Closure ve çevreleyen kod aynı değişkeni kullanır. Bir anlık görüntüye ihtiyacınız varsa closure'ı oluşturmadan önce değeri yeni bir değişkene kopyalayın ya da argüman olarak aktarın.

Go'da closure döngü değişkeni hatası düzeltildi mi?

Evet, Go 1.22'den beri. Bir for döngüsünün her yinelemesi artık yeni bir döngü değişkeni tanımlar, bu yüzden döngüde oluşturulan closure'lar ve goroutine'ler o yinelemenin değerini görür. go.mod dosyası go 1.22 ya da sonrasını belirten modüller için geçerlidir. Eski kodda geçici çözüm olarak döngünün içinde sıkça i := i görülür; zararsızdır ama artık gerekmez.

Coddy programming languages illustration

Coddy ile kodlamayı öğren

BAŞLA