defer ne yapar
defer bir fonksiyon çağrısını bir listeye ekler. Çevreleyen fonksiyon döndüğünde liste ters sırayla çalışır.
Çıktı:
start
end
deferred 3
deferred 2
deferred 1
Sıra bir stack gibi son giren ilk çıkar şeklindedir. Bu, kaynakların iç içe geçme biçimine uyar: önce A'yı sonra B'yi açarsanız, genellikle B'yi A'dan önce kapatmak istersiniz.
Temizliği edinmenin yanına koymak
defer'in asıl kullanımı, temizliği kaynağın edinildiği satırın hemen altına yazmaktır; böylece hiçbir dönüş yolu onu unutamaz.
Sıraya dikkat edin: önce hatayı kontrol edin, sonra defer yazın. os.Open başarısız olduysa f nil'dir ve kontrolden önce f.Close()'u ertelemek nil bir *os.File üzerinde Close çağırır (bu, hiç görmeyeceğiniz bir hata döndürür ve okuyanları yanıltır).
Aynı biçim kilitler için de işe yarar:
mu.Lock()
defer mu.Unlock()
Aradaki kod panic olsa bile mutex yine açılır.
Argümanlar hemen değerlendirilir
Ertelenen fonksiyon ve argümanları defer ifadesi çalıştığında değerlendirilir. Yalnızca çağrının kendisi bekler.
Çıktı:
x is now 2
deferred closure reads: 2
deferred with argument: 1
fmt.Println("...", x), defer satırında 1 değerini yakaladı. Closure'ın argümanı yoktur; x'i sonunda çalıştığında okur. Kaydetmek istediğiniz şeye uyan biçimi seçin.
Bu, metot alıcıları için de geçerlidir. defer t.Stop(), t'yi hemen değerlendirir, bu yüzden t'ye sonradan yeniden atama yapmak hangi değerin durdurulacağını değiştirmez.
Yaygın bir zamanlama hilesi bu kuralı bilerek kullanır:
func handle() {
defer trace("handle")() // trace runs now, the returned func runs at exit
// ...
}
trace("handle") hemen çağrılır ("enter" yazdırıp başlangıç zamanını kaydedebilir) ve ertelenen şey onun döndürdüğü fonksiyondur.
Döngüde defer
Ertelenmiş çağrılar her döngü yinelemesinin sonunda değil, fonksiyon döndüğünde çalışır. Birçok dosya üzerinde dönen bir döngüde bu, her dosyayı fonksiyon bitene kadar açık tutar.
for _, path := range paths {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close() // all files stay open until the function returns
process(f)
}
Binlerce yolla bu, dosya tanımlayıcılarını tüketir. Her defer'in yineleme başına çalışması için gövdeyi kendi fonksiyonuna taşıyın:
processFile'a yapılan her çağrı, bir sonraki dosya açılmadan önce kendi dosyasını kapatır. Adlandırılmış bir yardımcı fazla geliyorsa yerinde çağrılan bir fonksiyon literal'i (func() { ... }()) de aynı şekilde çalışır.
Dönüş değerlerini değiştirmek
Ertelenmiş bir closure, return ifadesi sonuçları atadıktan sonra çalışır ve çağıran görmeden önce adlı sonuçları değiştirebilir.
Bu 10 ve save failed: disk full yazdırır. Adsız bir sonuçla ertelenmiş bir fonksiyon yine çalışabilir, ama döndürülen değeri değiştirmenin hiçbir yolu yoktur.
Close'tan gelen hatayı yakalamak
defer f.Close(), Close'tan gelen hatayı atar. Yalnızca okuduğunuz dosyalar için bu sorun değildir. Yazdığınız dosyalarda Close başarısız bir son flush'ı raporlayabilir, bu yüzden hata önemlidir. Adlı bir sonuç onu tutmanızı sağlar:
func writeReport(path string, data []byte) (err error) {
f, err := os.Create(path)
if err != nil {
return err
}
defer func() {
if cerr := f.Close(); cerr != nil && err == nil {
err = cerr
}
}()
_, err = f.Write(data)
return err
}
İki hatanın da raporlanmasını istediğinizde errors.Join(err, f.Close()) daha kısa bir alternatiftir.
defer, panic ve recover
Ertelenmiş çağrılar bir panic stack'i geri sararken çalışır. recover'ın bir şey yaptığı tek yer burasıdır ve bir sunucunun tek bir hatalı isteğin süreci öldürmesini böyle önler. Ayrıntılar panic ve recover sayfasında.
Program os.Exit ya da log.Fatal ile çıktığında ertelenmiş çağrılar çalışmaz. main temizliği erteleyip sonra os.Exit(1) çağırırsa temizlik atlanır.
Maliyet
Go 1.14'ten beri çoğu defer derleyici tarafından open-coded olarak üretilir ve birkaç nanosaniyeye mal olur. Her mutex bırakma ve dosya kapatma için defer kullanmak normal stildir. Döngülerin içindeki defer'ler istisnadır: bunlar open-coded olamaz ve daha yavaş bir yola düşer; bu da döngü gövdelerini fonksiyonlara taşımak için bir neden daha.
Sık yapılan hatalar
- Hata kontrolünden önce defer yazmak. Önce
Open'dan gelenerr'i kontrol edin, sonradefer Closeyazın. - Döngüde yineleme başına temizlik beklemek. Defer'ler fonksiyondan çıkışta çalışır.
- Ertelenmiş bir argümanın sonraki değişiklikleri görmesini beklemek. Argümanlar
defersatırında sabitlenir. Çıkışta okumak için bir closure kullanın. os.Exitile defer'e güvenmek. Asla çalışmaz.
Sıkça Sorulan Sorular
Go'da defer ne işe yarar?
defer f(), f()'nin çevreleyen fonksiyon döndüğünde çalışmasını planlar; fonksiyon normal biçimde, erken bir return ile ya da panic ile dönse de fark etmez. Temizlik işlemini (bir dosyayı kapatmak, bir mutex'i açmak) kaynağı alan kodun hemen yanına koymak için kullanılır.
Go'da ertelenmiş çağrılar hangi sırayla çalışır?
Son giren ilk çıkar. En son ertelenen çağrı ilk çalışır. defer fmt.Println(1); defer fmt.Println(2) önce 2, sonra 1 yazdırır.
Ertelenmiş bir fonksiyonun argümanları ne zaman değerlendirilir?
Çağrı çalıştığında değil, defer ifadesi çalıştığı anda. x := 1; defer fmt.Println(x); x = 2 ifadesi 1 yazdırır. Değeri çıkış anında okumak için bir closure erteleyin: defer func() { fmt.Println(x) }().
defer panic'te ya da os.Exit'te çalışır mı?
Ertelenmiş çağrılar bir panic stack'i geri sararken çalışır; recover'ın onların içinde çalışmasının nedeni budur. Program os.Exit'i (ya da onu çağıran log.Fatal'ı) çağırdığında çalışmazlar ve main döndüğünde diğer goroutine'lerde de çalışmazlar.