Go'da fonksiyon func ad(parametreler) sonuçlar { ... } biçiminde tanımlanır ve birden çok değer döndürebilir; en yaygın kalıp, asıl sonucu ve bir error değerini birlikte döndürmektir: func bol(a, b int) (int, error). Sonuçlara ad verilebilir, son parametre ...T ile değişken sayıda argüman alabilir, fonksiyonlar değişkenlere atanıp argüman olarak geçirilebilir ve anonim fonksiyonlar çevrelerindeki değişkenleri yakalayan closure'lar oluşturur. Tüm argümanlar değerle (kopyalanarak) aktarılır.
Bu yazıda fonksiyon bildirimini, değerle aktarımın slice ve map için ne anlama geldiğini, çoklu dönüş değerlerini, isimli sonuçları, variadic parametreleri, fonksiyon tiplerini, closure'ları ve sık görülen derleyici hatalarını spesifikasyon ve Effective Go'ya dayanarak anlatıyoruz. Tüm örnekler Go 1.27.3 ile çalıştırıldı. Serinin haritası için Go öğrenme rehberi sayfasına bakabilirsiniz.
Fonksiyon bildirimi ve parametreler

Fonksiyon bildirimi bir adı bir fonksiyona bağlar. İmza, parametre listesinden ve isteğe bağlı sonuç listesinden oluşur. Aynı tipteki ardışık parametreler tip bir kez yazılarak gruplanabilir: func topla(a, b int) int. Spesifikasyona göre bir listedeki adlar ya hep yazılır ya hiç yazılmaz; tek ve adsız bir sonuç varsa parantezsiz yazılabilir. İmza sonuç bildiriyorsa fonksiyon gövdesi bir sonlandırıcı deyimle (return, panic, sonsuz for gibi) bitmek zorundadır; aksi hâlde derleyici eksik return hatası verir.
Go'da varsayılan parametre değeri ve aynı adla farklı imzalı fonksiyon (aşırı yükleme) yoktur. FAQ'ya göre aynı adlı ama farklı imzalı metotların bazen yararlı olduğu, ama pratikte kafa karıştırıcı ve kırılgan olabildiği görülmüş; yalnızca ada göre eşleştirme yapmak tip sisteminde büyük bir sadeleştirme sağlamıştır. Farklı davranışlar için farklı adlar (ParseInt, ParseFloat gibi) kullanılır.
Değerle aktarım: kopyalanan ne?
FAQ'nun belirttiği gibi C ailesindeki tüm dillerde olduğu gibi Go'da da her şey değerle aktarılır: fonksiyon, geçirilen şeyin bir kopyasını alır, sanki parametreye bir atama yapılmış gibi. Bir int geçirmek int'in, bir işaretçi geçirmek işaretçinin kopyasını oluşturur, ama işaret edilen veriyi kopyalamaz. Slice ve map değerleri ise işaretçi gibi davranır: alttaki veriye işaret eden tanımlayıcılardır, bu yüzden kopyalanmaları verinin kopyalanması anlamına gelmez. Dizi (array) ise bir değer tipidir ve tamamı kopyalanır.
package main
import "fmt"
func bol(s string, i int) (string, string) {
return s[:i], s[i:]
}
func birlestir(a, b string) string {
return b + a
}
func degistir(dizi [3]int, dilim []int, n int) {
dizi[0] = 100
dilim[0] = 100
n = 100
}
func faktoriyel(n int) int {
if n <= 1 {
return 1
}
return n * faktoriyel(n-1)
}
func main() {
fmt.Println(birlestir(bol("merhaba", 3))) // habamer
d := [3]int{1, 2, 3}
s := []int{1, 2, 3}
n := 1
degistir(d, s, n)
fmt.Println(d, s, n) // [1 2 3] [100 2 3] 1
fmt.Println(faktoriyel(10)) // 3628800
}Örnekteki degistir fonksiyonu üç parametrenin de ilk elemanını ya da değerini değiştirdi. Dizi ve tam sayı kopya olduğu için çağıran taraftaki değerler aynı kaldı; slice ise aynı alttaki diziyi gösterdiği için değişiklik dışarıdan görüldü. Aynı örnekte birlestir(bol("merhaba", 3)) çağrısı spesifikasyondaki özel bir kuralı kullanıyor: bir fonksiyonun dönüş değerleri sayı ve tip olarak başka bir fonksiyonun parametrelerine uyuyorsa, f(g()) biçiminde doğrudan aktarılabilir. Slice'ın bu davranışının ayrıntısı array ve slice farkı, işaretçilerle çalışmak ise pointer yazısında.
Çoklu dönüş değerleri

Effective Go çoklu dönüşü Go'nun alışılmadık özelliklerinden biri olarak tanıtır ve C'deki iki hantal kalıbı çözdüğünü söyler: hatayı -1 gibi özel bir değerle bildirmek ve sonucu döndürmek için adresle geçirilen bir argümanı değiştirmek. Go'da os.File'ın Write metodu hem yazılan bayt sayısını hem de bir hata döndürür. Kural olarak error son dönüş değeridir ve hata yoksa nil'dir.
package main
import (
"errors"
"fmt"
)
func topla(a, b int) int {
return a + b
}
func bol(a, b int) (int, int, error) {
if b == 0 {
return 0, 0, errors.New("sıfıra bölme")
}
return a / b, a % b, nil
}
func main() {
fmt.Println(topla(2, 3)) // 5
b, k, err := bol(17, 5)
fmt.Println(b, k, err) // 3 2 <nil>
_, _, err = bol(1, 0)
if err != nil {
fmt.Println("hata:", err) // hata: sıfıra bölme
}
}Dönen değerlerin hepsini karşılamak zorundasınız; ihtiyacınız olmayanları boş tanımlayıcı _ ile atabilirsiniz. İkinci çağrıda bölüm ve kalan atıldı, yalnızca hata kontrol edildi. Ayrıca err zaten tanımlı olduğu ve sol tarafta yeni değişken bulunmadığı için = kullanıldı. Hata değerleri üretmek, sarmalamak ve karşılaştırmak error ve hata yönetimi yazısının konusu.
İsimli sonuçlar ve yalın return
Sonuç parametrelerine ad verilebilir. Effective Go'ya göre isimli sonuçlar fonksiyon başladığında tiplerinin sıfır değerleriyle başlatılır ve sıradan değişkenler gibi kullanılır; fonksiyon argümansız bir return çalıştırırsa bu değişkenlerin o anki değerleri döndürülür. Adlar zorunlu değildir, ama bir belge işlevi görür: iki int döndüren bir fonksiyonda hangisinin ne olduğunu imzadan okumayı sağlarlar.
package main
import "fmt"
func boyutlar(cevre float64) (en, boy float64) {
en = cevre / 6
boy = en * 2
return // isimli sonuçların o anki değerleri döner
}
func say(s string) (harf, bosluk int) {
for _, r := range s {
if r == ' ' {
bosluk++
} else {
harf++
}
}
return harf, bosluk
}
func main() {
fmt.Println(boyutlar(30)) // 5 10
fmt.Println(say("Go dili çok hızlı")) // 14 3
}İlk fonksiyonda yalın return kullanıldı; ikincisinde isimli sonuçlar olmasına rağmen değerler açıkça yazıldı, bu da geçerlidir. Spesifikasyona göre sonuç değerleri nasıl bildirilirse bildirilsin, fonksiyona girerken sıfır değerleriyle başlatılır ve değer içeren bir return, ertelenmiş (defer) fonksiyonlar çalışmadan önce sonuç parametrelerini ayarlar. Bu ayrıntı, defer içinde isimli sonucu değiştirme tekniğinin temelidir ve defer, panic ve recover yazısında anlatılıyor.
Yalın return uzun fonksiyonlarda okunabilirliği düşürebilir, çünkü okuyucu hangi değerin döndüğünü görmek için yukarı bakmak zorunda kalır. Ayrıca spesifikasyon bir kısıt tanımlar: return noktasında isimli sonuçla aynı adda başka bir değişken kapsamdaysa (örneğin iç blokta err yeniden tanımlandıysa) derleyici yalın return'ü reddedebilir. Kısa ve anlamı açık fonksiyonlarda kullanmak en güvenli yaklaşımdır.
Variadic (değişken sayıda argümanlı) fonksiyonlar
İmzadaki son parametrenin tipi ...T ile yazılırsa fonksiyon variadic olur ve o parametre için sıfır ya da daha fazla argümanla çağrılabilir. Spesifikasyona göre fonksiyonun içinde bu parametrenin tipi []T'dir. Hiç argüman verilmezse parametrenin değeri nil olur; argüman verilirse elemanları bu argümanlar olan yeni bir slice oluşturulur. Elinizde zaten bir slice varsa, çağrıda sonuna ... ekleyerek onu olduğu gibi geçirebilirsiniz; bu durumda yeni slice oluşturulmaz ve fonksiyon aynı alttaki diziyi görür.
package main
import "fmt"
func toplam(etiket string, sayilar ...int) int {
fmt.Printf("%s: len=%d nil=%t\n", etiket, len(sayilar), sayilar == nil)
t := 0
for _, n := range sayilar {
t += n
}
return t
}
func sifirla(s ...int) {
s[0] = 0
}
func main() {
fmt.Println(toplam("boş"))
fmt.Println(toplam("üç", 1, 2, 3))
liste := []int{4, 5, 6}
fmt.Println(toplam("dilim", liste...))
sifirla(liste...)
fmt.Println(liste) // aynı dizi paylaşıldığı için ilk eleman değişti
// Çıktı:
// boş: len=0 nil=true
// 0
// üç: len=3 nil=false
// 6
// dilim: len=3 nil=false
// 15
// [0 5 6]
}Son satır bu kuralın sonucunu gösteriyor: sifirla(liste...) çağrısında fonksiyon slice'ın kopyasını değil aynı alttaki diziyi aldı, bu yüzden ilk eleman çağıran tarafta da sıfırlandı. fmt.Println ve append gibi standart fonksiyonlar da variadic'tir; append(a, b...) yazımının nedeni budur.
Fonksiyon tipleri ve fonksiyon değerleri
Go'da fonksiyonlar birinci sınıf değerlerdir: değişkene atanabilir, argüman olarak geçirilebilir, başka bir fonksiyondan döndürülebilir. Spesifikasyona göre bir fonksiyon tipi, aynı parametre ve sonuç tiplerine sahip tüm fonksiyonların kümesidir; başlatılmamış bir fonksiyon değişkeninin değeri nil'dir. type Donustur func(string) string gibi adlandırılmış fonksiyon tipleri, imzaları okunur kılar. Anonim fonksiyonlar (fonksiyon literal'leri) bir değişkene atanabilir ya da tanımlandıkları yerde hemen çağrılabilir.
package main
import (
"fmt"
"strings"
)
type Donustur func(string) string
func uygula(kelimeler []string, f Donustur) []string {
sonuc := make([]string, 0, len(kelimeler))
for _, k := range kelimeler {
sonuc = append(sonuc, f(k))
}
return sonuc
}
func main() {
k := []string{"go", "dil", "hız"}
fmt.Println(uygula(k, strings.ToUpper)) // [GO DIL HIZ]
fmt.Println(uygula(k, func(s string) string {
return "<" + s + ">"
})) // [<go> <dil> <hız>]
kare := func(x int) int { return x * x }
fmt.Println(kare(7)) // 49
fmt.Printf("%T\n", kare) // func(int) int
var bos func()
fmt.Println(bos == nil) // true
}Örnekte strings.ToUpper standart bir fonksiyon olarak, ikinci çağrıda ise yerinde yazılmış anonim bir fonksiyon olarak aynı parametreye geçirildi. Dikkat çekici bir ayrıntı: strings.ToUpper "dil" kelimesini Türkçe kurallara göre "DİL" değil "DIL" yaptı, çünkü varsayılan dönüşüm dil bağımsız Unicode kurallarını uygular. Türkçe harf dönüşümünün doğru yolu string, rune, byte ve UTF-8 yazısında. Fonksiyonlara benzeyen ama bir tipe bağlı olan metotlar ise metotlar ve alıcı tipleri yazısının konusu.
Closure: çevredeki değişkeni yakalayan fonksiyon

Spesifikasyona göre fonksiyon literal'leri closure'dır: çevrelerindeki fonksiyonda tanımlanmış değişkenlere başvurabilirler. Bu değişkenler çevreleyen fonksiyon ile literal arasında paylaşılır ve erişilebildikleri sürece yaşamaya devam eder; çevreleyen fonksiyon dönmüş olsa bile. Bu sayede durum tutan fonksiyonlar yazılabilir. Aşağıdaki sayacUret her çağrıldığında yeni bir n değişkeni oluşturur ve onu yakalayan bir fonksiyon döndürür; iki sayaç birbirinden bağımsızdır. Fibonacci üreteci de iki değişkeni turdan tura taşıyor.
package main
import "fmt"
func sayacUret() func() int {
n := 0
return func() int {
n++
return n
}
}
func fibonacci() func() int {
a, b := 0, 1
return func() int {
s := a
a, b = b, a+b
return s
}
}
func main() {
c1 := sayacUret()
c2 := sayacUret()
fmt.Println(c1(), c1(), c1()) // 1 2 3
fmt.Println(c2()) // 1
f := fibonacci()
for range 8 {
fmt.Print(f(), " ")
}
fmt.Println() // 0 1 1 2 3 5 8 13
}Closure'ların değişkeni kopyalamadığını, paylaştığını unutmamak önemlidir. Closure dışarıdaki değişkeni değiştirirse değişiklik her yerden görülür; değişken başka bir yerde değişirse closure yeni değeri görür. Go 1.22 öncesindeki döngü değişkeni hatası tam olarak bu paylaşımdan kaynaklanıyordu; for döngüsü ve range yazısında aynı programı eski ve yeni kurallarla çalıştırarak gösterdik. Closure'ları goroutine olarak çalıştırdığınızda paylaşılan değişkenlere erişimi ayrıca senkronize etmeniz gerekir; bu konu goroutine yazısında.
Fonksiyonlar kendilerini çağırabilir; yukarıdaki faktoriyel örneği özyinelemenin basit bir kullanımıdır. Tip parametreli generic fonksiyonlar ise ayrı bir konudur: spesifikasyona göre fonksiyon literal'leri tip parametresi bildiremez; generic fonksiyonlar generics ve tip parametreleri yazısında anlatılıyor.
Mini örnek: fonksiyon tablosuyla hesap makinesi
Fonksiyonların değer olması, davranışı veri gibi saklamayı sağlar. Aşağıdaki küçük hesap makinesi dört işlemi, anahtarı işlem sembolü ve değeri bir fonksiyon olan bir map'te tutuyor. Tüm fonksiyonlar aynı islem tipine uyduğu için aynı map'e konabiliyor. Bölme fonksiyonu sıfıra bölmeyi kontrol edip hata döndürüyor; bilinmeyen bir sembol için ise hesapla fonksiyonu map'in virgül-ok kalıbıyla anahtarın var olup olmadığını denetliyor ve fmt.Errorf ile açıklayıcı bir hata üretiyor.
package main
import (
"errors"
"fmt"
)
type islem func(a, b float64) (float64, error)
func islemler() map[string]islem {
return map[string]islem{
"+": func(a, b float64) (float64, error) { return a + b, nil },
"-": func(a, b float64) (float64, error) { return a - b, nil },
"*": func(a, b float64) (float64, error) { return a * b, nil },
"/": func(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("sıfıra bölme")
}
return a / b, nil
},
}
}
func hesapla(op string, a, b float64) (sonuc float64, err error) {
f, ok := islemler()[op]
if !ok {
return 0, fmt.Errorf("bilinmeyen işlem: %q", op)
}
return f(a, b)
}
func main() {
for _, op := range []string{"+", "/", "%"} {
s, err := hesapla(op, 9, 0)
if err != nil {
fmt.Println(op, "hata:", err)
continue
}
fmt.Println(op, s)
}
// Çıktı:
// + 9
// / hata: sıfıra bölme
// % hata: bilinmeyen işlem: "%"
}hesapla fonksiyonundaki return f(a, b) satırı, spesifikasyondaki bir kuralı kullanıyor: return deyiminin ifade listesi, çok değerli tek bir fonksiyon çağrısı olabilir. Bu durumda çağrının döndürdüğü değerler sırasıyla sonuç parametrelerine atanır. İsimli sonuçlar (sonuc, err) burada yalnızca belge işlevi görüyor; tüm return deyimleri değerleri açıkça yazıyor. Döngüdeki continue ise hatalı işlemde döngünün geri kalanını atlıyor. Map'lerin ayrıntılı kullanımı map kullanımı yazısında.
Sık yapılan hatalar
1. Her yolda return yazmamak. Sonuç bildiren bir fonksiyonun gövdesi sonlandırıcı bir deyimle bitmelidir. if-else if zincirinin sonunda else ya da return yoksa derleyici, bazı yollarda dönüş değeri olmadığı için kodu reddeder:
package main
import "fmt"
func isaret(n int) string {
if n > 0 {
return "pozitif"
} else if n < 0 {
return "negatif"
}
}
func main() {
fmt.Println(isaret(1))
}# command-line-arguments
./main.go:11:1: missing returnÇözüm, zincirin sonuna bir return eklemek ya da son dalı else yapmaktır.
2. Çoklu dönüşün bir değerini tek değişkene almak. İki değer döndüren bir fonksiyonu tek değişkene atayamazsınız; ya hepsini karşılayın ya da gereksiz olanı _ ile atın:
package main
import "strconv"
func main() {
n := strconv.Atoi("12")
_ = n
}# command-line-arguments
./main.go:6:7: assignment mismatch: 1 variable but strconv.Atoi returns 2 values3. nil fonksiyon değerini çağırmak. Spesifikasyona göre nil bir fonksiyon değerini çağırmak çalışma zamanında panic üretir. Derleyici bunu yakalayamaz; program çağrıya kadar çalışır:
package main
import "fmt"
func main() {
var islem func(int) int
fmt.Println("çağrılıyor")
fmt.Println(islem(3))
}çağrılıyor
panic: runtime error: invalid memory address or nil pointer dereferenceBir fonksiyon değişkeni isteğe bağlıysa (örneğin bir geri çağırma), çağırmadan önce if islem != nil ile kontrol edin.
4. Variadic parametreye geçirilen slice'ın kopyalandığını sanmak. f(s...) çağrısında fonksiyon aynı alttaki diziyi görür; fonksiyonun elemanları değiştirmesi çağıranın slice'ını da değiştirir. Bağımsız bir kopya gerekiyorsa çağırmadan önce kopyalayın.
5. Hata değerini _ ile yok saymak. Dönüş değerini boş tanımlayıcıyla atmak sözdizimi olarak mümkündür, ama Effective Go hatayı yok saymayı kötü uygulama olarak niteler: hata kontrol edilmezse program daha sonra anlamsız bir değerle ya da panic ile karşılaşabilir.
Sık Sorulan Sorular
Go'da varsayılan parametre değeri var mı?
Hayır. Go'da varsayılan parametre ve aşırı yükleme yoktur. Yaygın çözümler; farklı adlı fonksiyonlar yazmak, ayarları bir struct ile geçirmek (sıfır değerleri varsayılan kabul ederek) ya da variadic seçenek fonksiyonları kullanmaktır.
İsimli sonuç kullanmalı mıyım?
Aynı tipte birden çok sonuç döndüren fonksiyonlarda adlar, hangi değerin ne olduğunu belgelediği için faydalıdır. Yalın return'ü ise kısa fonksiyonlarla sınırlamak okunabilirliği korur.
Slice parametresini değiştirmek çağıranı etkiler mi?
Elemanları değiştirmek etkiler, çünkü slice alttaki diziye işaret eden bir tanımlayıcıdır ve kopyası aynı diziyi gösterir. Ancak fonksiyon içinde slice'a append yapıp yeni bir dizi oluşursa, bu çağıranın slice değişkenine yansımaz; ayrıntısı append ve copy yazısında.
Fonksiyonlar karşılaştırılabilir mi?
Fonksiyon değerleri yalnızca nil ile karşılaştırılabilir. İki fonksiyon değerinin aynı fonksiyonu gösterip göstermediğini == ile kontrol edemezsiniz.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: Go öğrenme rehberi · Go defer, panic ve recover · Go hata yönetimi · Go metotlar · Go generics · Go for döngüsü ve range