Go'da değişken iki yolla tanımlanır: her yerde kullanılabilen var ad tip = değer bildirimi ve yalnızca fonksiyon içinde geçerli olan kısa bildirim ad := değer. Değer vermeden tanımlanan her değişken tipinin sıfır değerini alır (sayılar için 0, string için boş metin, bool için false, işaretçi, slice ve map için nil). Sabitler ise const ile tanımlanır, derleme zamanında hesaplanır ve yalnızca sayı, rune, string ya da bool olabilir.

Bu yazıda bildirim biçimlerini, sıfır değerleri, kısa bildirimdeki yeniden bildirim kuralını, kapsam ve gölgeleme (shadowing) tuzağını, tipsiz sabitlerin esnekliğini ve iota ile sıralı sabit üretmeyi spesifikasyondaki kurallara dayanarak anlatıyoruz. Tüm örnekler Go 1.27.3 ile çalıştırıldı. Konunun serideki yeri için Go öğrenme rehberi sayfasına bakabilirsiniz.

var ile değişken bildirimi

Spesifikasyona göre değişken, belirli bir tipte değer tutan bir bellek konumudur. var bildirimi bir ya da daha fazla değişken oluşturur, her birine bir tip ve bir başlangıç değeri verir. Tip ve değer birlikte yazılabilir, yalnızca biri de yazılabilir: tip yazılmazsa başlangıç değerinin tipi kullanılır; değer yazılmazsa değişken sıfır değeriyle başlar. Birden fazla değişkeni tek satırda ya da parantez içinde gruplayarak tanımlamak mümkündür.

go
package main

import "fmt"

var surum = "1.27" // paket düzeyinde değişken

func main() {
	var sayac int
	var oran float64
	var ad string
	var aktif bool
	var dilim []int
	var harita map[string]int
	var ptr *int

	fmt.Println(sayac, oran, aktif)                      // 0 0 false
	fmt.Printf("%q\n", ad)                               // ""
	fmt.Println(dilim == nil, harita == nil, ptr == nil) // true true true

	var x, y = 10, "on"
	z := 3.5
	fmt.Printf("%T %T %T\n", x, y, z) // int string float64

	var (
		host = "localhost"
		port = 8080
	)
	fmt.Println(host, port, surum) // localhost 8080 1.27
}

Bu örnekte birkaç önemli ayrıntı var. Paket düzeyindeki surum değişkeni fonksiyon dışında tanımlandığı için paketin tüm dosyalarından görülebilir. Fonksiyon içindeki değişkenlerin hiçbirine değer verilmediği hâlde program sorunsuz çalışır, çünkü Go'da başlatılmamış değişken diye bir şey yoktur. %T fiili değişkenin tipini yazdırır: tipsiz 10 sabiti int, 3.5 ise float64 olarak yorumlandı. Bunun nedeni aşağıda anlatılan varsayılan tip kuralıdır. Printf fiillerinin tamamı fmt ile Printf biçimlendirme yazısında.

Sıfır değer: başlatılmamış değişken yoktur

Sıfır değer: başlatılmamış değişken yoktur: int, float64 ve diğer sayısal tipler, bool, string, işaretçi, fonksiyon, arayüz

Spesifikasyon, bir değişken için bellek ayrıldığında ya da yeni bir değer oluşturulduğunda açık bir başlangıç değeri verilmemişse her öğenin tipinin sıfır değerine ayarlandığını söyler. Bu kural özyinelidir: bir struct dizisinin her elemanının her alanı da sıfırlanır. Bu sayede var i int ile var i int = 0 tamamen eşdeğerdir.

TipSıfır değer
int, float64 ve diğer sayısal tipler0
boolfalse
string"" (boş metin)
işaretçi, fonksiyon, arayüznil
slice, map, channelnil
array ve structher eleman ve alan kendi sıfır değeriyle

Sıfır değerin pratik sonucu, birçok tipin hiç başlatılmadan kullanılabilmesidir. Örneğin nil bir slice'a append yapılabilir, nil bir slice'ın uzunluğu sıfırdır ve üzerinde range hiç dönmez. Buna karşılık nil bir map'e yazmak çalışma zamanında panic üretir; bu ayrım map kullanımı yazısında örnekleriyle ele alınıyor. Go'da tipleri tasarlarken sıfır değerinin anlamlı ve kullanılabilir olmasına özen gösterilir.

Kısa değişken bildirimi (:=)

Kısa değişken bildirimi (:=)

Kısa bildirim ad := değer, tip yazılmamış bir var ad = değer bildiriminin kısaltmasıdır ve yalnızca fonksiyonların içinde kullanılabilir. if, for ve switch deyimlerinin başlangıç kısımlarında da geçici değişken tanımlamak için kullanılır. Go kodunda en sık göreceğiniz bildirim biçimi budur.

Kısa bildirimin var'dan önemli bir farkı vardır: yeniden bildirim. Sol tarafta birden çok değişken varsa, bunlardan bazıları aynı blokta daha önce aynı tiple tanımlanmış olabilir; yeter ki en az biri yeni olsun. Bu durumda eski değişken yeniden yaratılmaz, yalnızca yeni değer atanır. Effective Go bunu tamamen pratik bir kural olarak tanımlar: uzun bir işlem zincirinde tek bir err değişkenini tekrar tekrar kullanabilmeyi sağlar.

go
package main

import (
	"fmt"
	"strconv"
)

func main() {
	a, err := strconv.Atoi("42")
	b, err := strconv.Atoi("x") // err yeniden atanır, b yeni
	fmt.Println(a, b, err)
	// 42 0 strconv.Atoi: parsing "x": invalid syntax

	x := 1
	if true {
		x := 2 // yeni değişken: dıştaki x gölgelenir
		x++
		fmt.Println("iç:", x) // iç: 3
	}
	fmt.Println("dış:", x) // dış: 1
}

İlk satırda a ve err yeni tanımlandı. İkinci satırda b yeni olduğu için bildirim geçerlidir ve err yalnızca yeni bir değer aldı. Sol taraftaki değişkenlerin hiçbiri yeni değilse derleyici bunu kabul etmez; aşağıda bunu göreceğiz.

Kapsam ve gölgeleme

Go, bloklara dayalı sözcüksel kapsam kullanır. Kaynaktaki her süslü parantez çifti bir blok açar; bunlara ek olarak her if, for ve switch deyimi ile switch ve select içindeki her case dalı kendi örtük bloğunu oluşturur. Fonksiyon içinde tanımlanan bir değişkenin kapsamı, tanımın bittiği yerde başlar ve onu içeren en içteki bloğun sonunda biter. Paket düzeyinde tanımlanan değişken, sabit, tip ve fonksiyonların kapsamı ise paket bloğunun tamamıdır.

Spesifikasyon, bir blokta tanımlanmış bir adın iç blokta yeniden tanımlanabileceğini söyler. İç tanım kapsamdayken ad, dıştakini değil içtekini gösterir. Buna gölgeleme (shadowing) denir ve yukarıdaki örnekte if bloğu içindeki x := 2 satırı tam olarak bunu yapar: içeride arttırılan değişken dıştaki x değil, yeni bir değişkendir. Bu yüzden dışarıda x hâlâ 1'dir.

Gölgeleme çoğu zaman kazara olur ve en sık err değişkeniyle görülür. Dış kapsamda tanımlı bir err varken iç blokta v, err := f() yazmak, dıştaki err'i güncellemez; iç blokta yeni bir err yaratır. Blok bittiğinde dıştaki değişken eski değerinde kalır. Effective Go'nun dipnotu bu kuralın sınırını netleştirir: yeniden bildirim yalnızca aynı kapsamda olur; değişken dış kapsamda tanımlıysa := yeni bir değişken oluşturur. Fonksiyon parametreleri ve isimli sonuçlar ise, süslü parantezlerin dışında yazılmalarına rağmen fonksiyon gövdesiyle aynı kapsamdadır.

Boş tanımlayıcı ve kullanılmayan değişkenler

Alt çizgi (_) boş tanımlayıcıdır: bir değişken gereken yerde yer tutucu olarak kullanılır ama hiçbir bağ oluşturmaz, atanan değer atılır. Çoklu dönüş değerlerinden birine ihtiyacınız olmadığında ya da range döngüsünde indisi kullanmadığınızda işe yarar. Effective Go, hata değerini boş tanımlayıcıyla yok saymayı ise açıkça kötü uygulama olarak niteler; dönen hatalar bir nedenle vardır.

Go derleyicisi, fonksiyon içinde tanımlanıp hiç kullanılmayan değişkeni hata olarak bildirir. Spesifikasyon bunu bir uygulama kısıtı olarak tanımlar ve FAQ gerekçesini açıklar: kullanılmayan bir değişken çoğu zaman bir hatanın işaretidir ve Go derleyicisi uyarı üretmez, yalnızca derlemeyi durduran hata üretir. Paket düzeyindeki kullanılmayan değişkenler ise hata vermez.

go
package main

import "fmt"

func main() {
	x := 5
	fmt.Println("merhaba")
}
text
# command-line-arguments
./main.go:6:2: declared and not used: x

Geliştirme sırasında geçici olarak bir değeri saklamanız gerekiyorsa FAQ'nun önerdiği yol _ = x atamasıdır, ama bunu kalıcı bırakmamak gerekir.

Sabitler: const, tipli ve tipsiz

Sabitler: const, tipli ve tipsiz

Sabitler const ile tanımlanır ve değerleri derleme zamanında bilinmek zorundadır. Effective Go'ya göre fonksiyon içinde tanımlansalar bile derleme sırasında oluşturulurlar ve yalnızca sayı, karakter (rune), string ya da bool olabilirler. 1<<3 bir sabit ifadedir; math.Sin(math.Pi/4) ise değildir, çünkü fonksiyon çağrısı çalışma zamanında yapılır. Slice, map ya da struct sabiti tanımlanamaz.

Sabitlerin en ilginç yönü tipsiz olabilmeleridir. Spesifikasyona göre sabit değerler, literal sabitler, true, false ve iota tipsizdir; sayısal sabitler keyfi hassasiyetle tam olarak temsil edilir ve taşmaz. Tipsiz bir sabit, tip gerektiren bir bağlamda kullanıldığında o tipe dönüştürülür. Tip belirtilmeyen bir bağlamda (örneğin i := 0) ise varsayılan tipine çevrilir: bool, rune, int, float64, complex128 ya da string. Bir sabit const Pi float64 = 3.14159 gibi açık tiple tanımlanırsa tipli sabit olur ve yalnızca o tiple uyumlu yerlerde kullanılabilir.

FAQ bu esnekliği şöyle özetler: Go sayısal tipler arasında katıdır, ama sabitler ideal bir sayı uzayında yaşar. math.Sqrt(2) yazabilmenizin nedeni, tipsiz 2'nin güvenle float64'e çevrilebilmesidir. Aşağıdaki örnekte aynı tipsiz t sabiti hem float64 hem int32 değişkene atanabiliyor. 1 << 100 gibi hiçbir yerleşik tipe sığmayan bir değer de sabit olarak tanımlanabilir; yeter ki kullanıldığı yerde sonuç hedef tipe sığsın.

iota ile sıralı sabitler

Parantezli bir const grubunda önceden tanımlı iota tanımlayıcısı, her sabit satırında bir artan tipsiz tam sayıdır ve sıfırdan başlar. Spesifikasyon iki kuralı birleştirir: gruptaki bir satırda ifade yazılmazsa önceki satırın ifadesi (ve tipi) aynen tekrarlanmış sayılır; iota ise her satırda yeni değerini alır. Bu ikisi birlikte, sıralı değerleri tek satırla tanımlamayı sağlar. Aynı satırda birden çok iota geçerse hepsi aynı değeri taşır; her yeni const bildirimi ise iota'yı sıfırdan başlatır.

go
package main

import "fmt"

type Gun int

const (
	Pazartesi Gun = iota
	Sali
	Carsamba
	Persembe
	Cuma
)

type Boyut int64

const (
	_        = iota // 0 değerini atla
	KB Boyut = 1 << (10 * iota)
	MB
	GB
)

const Buyuk = 1 << 100

func main() {
	fmt.Println(Pazartesi, Carsamba, Cuma) // 0 2 4
	fmt.Printf("%T\n", Sali)               // main.Gun
	fmt.Println(KB, MB, GB)                // 1024 1048576 1073741824
	fmt.Println(Buyuk >> 98)               // 4

	const t = 10 // tipsiz sabit
	var f float64 = t
	var i int32 = t
	fmt.Println(f, i) // 10 10
}

Gun örneğinde yalnızca ilk satıra tip ve iota yazıldı; diğer satırlar bu ifadeyi tekrarladığı için hepsi Gun tipinde ve sırasıyla 0'dan 4'e kadar değer aldı. Boyut örneğinde ilk satır _ ile atlandı, böylece KB satırında iota 1 oldu ve 1 << (10 * iota) ifadesi sırasıyla 1024, 1048576 ve 1073741824 değerlerini üretti. Effective Go'daki ByteSize örneği de aynı tekniği kullanır ve tipe bir String metodu ekleyerek değerlerin okunur biçimde yazdırılmasını sağlar; bu kalıp arayüzler yazısındaki Stringer konusunda karşınıza çıkacak.

Sık yapılan hatalar

1. Aynı blokta := ile yalnızca var olan değişkeni tekrar bildirmek. Sol tarafta hiç yeni değişken yoksa derleyici kısa bildirimi reddeder. Değer değiştirmek istiyorsanız = kullanın.

go
package main

import "fmt"

func main() {
	a := 1
	a := 2
	fmt.Println(a)
}
text
# command-line-arguments
./main.go:7:4: no new variables on left side of :=

2. Fonksiyon dışında := kullanmak. Kısa bildirim yalnızca fonksiyon gövdesinde geçerlidir. Paket düzeyinde her deyim bir anahtar sözcükle (var, const, type, func, import) başlamak zorundadır:

go
package main

import "fmt"

port := 8080

func main() {
	fmt.Println(port)
}
text
# command-line-arguments
./main.go:5:1: syntax error: non-declaration statement outside function body

3. Tipli bir değişkene sığmayan sabit atamak. Tipsiz sabitler esnek olsa da sonuçta hedef tipe sığmaları gerekir. int8 en fazla 127 tutabildiği için 300 değeri derleme zamanında reddedilir; bu kontrol çalışma zamanına kalmaz:

text
# command-line-arguments
./main.go:7:15: cannot use 300 (untyped int constant) as int8 value in variable declaration (overflows)

4. Tip belirtmeden nil ile başlatmak. Spesifikasyona göre nil tipsizdir ve tipi olmayan bir değişkeni başlatmak için kullanılamaz; var n = nil derlenmez. Hangi tipte nil istediğinizi yazmanız gerekir, örneğin var n *int ya da var hata error.

text
# command-line-arguments
./main.go:6:10: use of untyped nil in variable declaration

5. err gölgelemesini fark etmemek. Dış kapsamda bir err varken iç blokta := kullanmak yeni bir değişken yaratır ve dıştaki hata hiç güncellenmez. İç blokta mevcut değişkene atamak istiyorsanız değişkenleri önceden tanımlayıp = kullanın.

Sık Sorulan Sorular

var ile := arasında hangisini kullanmalıyım?

Fonksiyon içinde, başlangıç değeri olan değişkenler için deyimsel tercih :='dır. Değişkenin sıfır değerle başlaması gerektiğinde (var toplam int), tipin başlangıç değerinden farklı olmasını istediğinizde (var f float32 = 3) ya da paket düzeyinde tanım yaptığınızda var kullanılır.

Go'da sabit dizi ya da sabit map tanımlayabilir miyim?

Hayır. Sabitler yalnızca sayı, rune, string ve bool olabilir ve derleme zamanında hesaplanmalıdır. Değişmemesi gereken bir tablo için paket düzeyinde bir değişken tanımlayıp onu dışa aktarmamak ya da değeri döndüren bir fonksiyon yazmak yaygın yaklaşımdır.

Kullanılmayan değişken neden uyarı değil de hata?

FAQ'ya göre Go derleyicisi uyarı üretmez; bir şey şikâyet etmeye değerse düzeltmeye de değerdir. Kullanılmayan değişken çoğu zaman bir hatanın belirtisidir. Geçici durumlar için _ = x ataması derlemeyi sürdürmenizi sağlar.

iota'yı sıfır yerine birden başlatabilir miyim?

Evet. İlk satırı boş tanımlayıcıyla atlayabilir (_ = iota) ya da ifadeye ekleme yapabilirsiniz (A = iota + 1). Sonraki satırlar aynı ifadeyi tekrarladığı için hepsi birden başlayarak artar.

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 temel veri tipleri · Go if ve switch · Go fonksiyonlar · Go pointer · Go sık karşılaşılan hatalar