Go'da karşılaşacağınız hatalar iki gruba ayrılır: derleyicinin programı hiç çalıştırmadan reddettiği derleme hataları (declared and not used, imported and not used, missing return, mismatched types, undefined) ve program çalışırken ortaya çıkan, çoğu panic: ile başlayan çalışma zamanı hataları (assignment to entry in nil map, index out of range, nil pointer dereference, all goroutines are asleep - deadlock!). Bu yazıda en sık görülen mesajların her birini, Go 1.27.3'ün gerçekte yazdırdığı biçimiyle, nedeni ve düzeltmesiyle birlikte görüyorsunuz.

Mesajlar dosya adı, satır ve sütun bilgisiyle gelir: ./main.go:9:2: dokuzuncu satırın ikinci sütunu demektir. Windows'ta yol .\main.go biçiminde yazılır; yazıda Linux biçimini kullandık. go run ile tek dosya çalıştırdığınızda derleme hatalarının üstünde # command-line-arguments satırı görünür; bu, hatanın komut satırında verilen dosyalardan oluşan paket için olduğunu gösterir. Kurulum ve ilk program için Go kurulumu yazısına bakabilirsiniz.

Hata mesajları özet tablosu

MesajTürKısa çözüm
declared and not used: xderlemedeğişkeni kullanın, silin ya da _ ile atın
"os" imported and not usedderlemeimportu silin; goimports ya da gopls kullanın
missing returnderlemeher yolun sonuna return ekleyin
mismatched types int and float64derlemeaçık tip dönüşümü yapın
undefined: xderlemeyazımı ve kapsamı kontrol edin
no new variables on left side of :=derleme= ile atama yapın
does not implement (method has pointer receiver)derlemeişaretçi (&T) kullanın
assignment to entry in nil mappanicmap'i make ile oluşturun
index out of range [5] with length 3paniclen ile sınırı denetleyin
invalid memory address or nil pointer dereferencepanicnil kontrolü yapın
interface conversion: ... is string, not intpaniccomma-ok tip denetimi kullanın
all goroutines are asleep - deadlock!fatal errorgönderen ve alanı eşleştirin, kanalı kapatın
concurrent map writesfatal errorMutex ya da sync.Map kullanın

declared and not used ve imported and not used

declared and not used ve imported and not used

Go, kullanılmayan yerel değişkenleri ve importları uyarı değil hata sayar. Yeni başlayanların ilk karşılaştığı mesajlar genellikle bunlardır:

go
package main

import (
	"fmt"
	"os"
)

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

Go FAQ'nun açıklaması şöyledir: kullanılmayan bir değişken bir hatanın belirtisi olabilir, kullanılmayan importlar ise derlemeyi yavaşlatır ve kod büyüdükçe bu etki ciddileşir. Derleyicinin uyarı üretmemesi de bilinçli bir tercihtir; FAQ'ya göre bir şey şikâyet etmeye değerse düzeltmeye de değer. Geliştirme sırasında bir değişkeni geçici olarak tutmak isterseniz boş tanımlayıcıya atayabilirsiniz: _ = x. Importlar için FAQ, dosyayı kaydederken importları otomatik düzelten goimports aracını ya da aynı işi yapan gopls dil sunucusunu önerir. Değişken tanımlama kuralları değişkenler ve sabitler yazısında.

missing return, mismatched types ve undefined

Aşağıdaki küçük dosyada üç ayrı derleme hatası var. Derleyici hepsini aynı anda bildirir, bu yüzden listenin tamamını okumak tek tek düzeltip yeniden derlemekten hızlıdır.

go
package main

import "fmt"

func bol(a, b int) int {
	if b != 0 {
		return a / b
	}
}

func main() {
	var toplam int = 3
	var oran float64 = 1.5
	fmt.Println(toplam * oran)
	fmt.Println(bol(6, 3), sayi)
}
text
# command-line-arguments
./main.go:9:1: missing return
./main.go:14:14: invalid operation: toplam * oran (mismatched types int and float64)
./main.go:15:25: undefined: sayi

missing return: bol fonksiyonu bir int döndüreceğini söylüyor, ama b == 0 olduğunda hiçbir return'e ulaşmadan bitiyor. Derleyici fonksiyonun her yolunun bir dönüş deyimiyle bittiğinden emin olmak ister; if bloğundan sonra bir return 0 ya da bir hata döndürmek gerekir. Daha iyisi, fonksiyonu (int, error) döndürecek biçimde yazmaktır; bunun için çoklu dönüş ve hata yönetimi yazılarına bakın.

mismatched types int and float64: Go, farklı sayısal tipler arasında örtük dönüşüm yapmaz. int ile float64'ü çarpmak için birini açıkça dönüştürmelisiniz: float64(toplam) * oran. Bu kural int ile int64 arasında da geçerlidir. Tip dönüşümünün ayrıntıları temel veri tipleri yazısında. undefined: sayi ise ya bir yazım hatasıdır ya da isim bu noktada görünür bir kapsamda tanımlı değildir.

Kapsam hataları: undefined ve no new variables

Bir değişkenin hangi bloklarda görünür olduğu (kapsamı) iki yaygın hataya yol açar. if x := 5; x > 3 biçimindeki kısa tanımın değişkeni yalnızca if (ve varsa else) bloğunda yaşar; bloktan çıkınca yoktur. := ise solunda en az bir yeni değişken ister; hepsi zaten tanımlıysa atama için = kullanmalısınız.

go
package main

import "fmt"

func main() {
	if x := 5; x > 3 {
		fmt.Println("büyük")
	}
	fmt.Println(x)
	a, b := 1, 2
	a, b := 3, 4
	fmt.Println(a, b)
}
text
# command-line-arguments
./main.go:9:14: undefined: x
./main.go:11:7: no new variables on left side of :=

:= ile ilgili daha sinsi bir durum da gölgelemedir: iç bir blokta aynı adla yeniden := yazarsanız derleyici hata vermez, yeni bir değişken oluşturur ve dıştaki değişken değişmeden kalır. Bu konu değişkenler yazısındaki gölgeleme bölümünde ve if ve switch yazısında anlatılıyor.

does not implement: işaretçi alıcılı metot

Bir tip, bir arayüzün tüm metotlarına sahipse o arayüzü örtük olarak uygular. Ancak metot işaretçi alıcıyla (func (k *Kare)) tanımlandıysa, bu metot yalnızca *Kare tipinin metot kümesindedir; Kare değeri arayüzü karşılamaz.

go
package main

import "fmt"

type Sekil interface {
	Alan() float64
}

type Kare struct{ Kenar float64 }

func (k *Kare) Alan() float64 { return k.Kenar * k.Kenar }

func main() {
	var s Sekil = Kare{2}
	fmt.Println(s.Alan())
}
text
# command-line-arguments
./main.go:14:16: cannot use Kare{…} (value of struct type Kare) as Sekil value in variable declaration: Kare does not implement Sekil (method Alan has pointer receiver)

Mesajın sonundaki parantez nedeni açıkça söylüyor: method Alan has pointer receiver. Çözüm arayüze değeri değil işaretçiyi koymaktır: var s Sekil = &Kare{2}. Ya da metodun alıcıyı değiştirmediği durumlarda değer alıcı (func (k Kare)) kullanılabilir. Metot kümeleri metotlar ve alıcı tipleri, arayüzler interface rehberinde ayrıntılı anlatılıyor.

assignment mismatch, cannot assign ve nil

assignment mismatch, cannot assign ve nil

İki değer döndüren bir fonksiyonun sonucunu tek bir değişkene alamazsınız; Go dönen değerlerin hepsini karşılamanızı ister. Gerekmeyenleri _ ile atabilirsiniz: x, _ := ikiDeger(). Ancak dönen bir error'ı bu şekilde yok saymak çoğu zaman kötü bir fikirdir.

go
package main

import "fmt"

func ikiDeger() (int, error) { return 1, nil }

func main() {
	x := ikiDeger()
	fmt.Println(x)
}
text
# command-line-arguments
./main.go:8:7: assignment mismatch: 1 variable but ikiDeger returns 2 values

String'ler değiştirilemez (immutable) olduğu için s[0] = 'K' derlenmez; yeni bir string oluşturmanız ya da önce []byte veya []rune'a dönüştürmeniz gerekir. nil ise yalnızca işaretçi, fonksiyon, dilim, map, kanal ve arayüz tipleri için geçerli bir sıfır değerdir; int gibi bir tipe atanamaz.

go
package main

import "fmt"

func main() {
	s := "kedi"
	s[0] = 'K'
	var n int = nil
	fmt.Println(s, n)
}
text
# command-line-arguments
./main.go:7:2: cannot assign to s[0] (neither addressable nor a map index expression)
./main.go:8:14: cannot use nil as int value in variable declaration

String'lerin bayt ve rune yapısı için string, rune ve UTF-8 yazısına bakabilirsiniz.

Çalışma zamanı: nil map, dizin taşması ve nil işaretçi

Derleme başarılı olsa bile bazı hatalar ancak çalışma sırasında ortaya çıkar. Çalışma zamanı bu durumda bir panic başlatır, ilk satırda nedeni yazdırır, ardından goroutine yığın izini (stack trace) basar ve program 2 çıkış koduyla biter. Aşağıdaki örneklerde yalnızca ilk satırı gösteriyoruz; asıl aranacak bilgi, yığın izinde main.go dosyanızdaki satır numarasıdır.

go
package main

func main() {
	var sayac map[string]int
	sayac["a"]++
}

// Çıktı:
// panic: assignment to entry in nil map

Bir map değişkenini yalnızca var ile tanımlamak onu nil bırakır. Nil map'ten okumak sorunsuzdur ve sıfır değer döner, ama yazmak panic olur. Map'i make(map[string]int) ya da map[string]int{} ile oluşturun. Struct alanı olan map'leri kurucu fonksiyonda başlatmayı unutmak bu hatanın en yaygın kaynağıdır. Ayrıntılar map kullanımı yazısında.

go
package main

import "fmt"

func main() {
	s := []int{1, 2, 3}
	i := 5
	fmt.Println(s[i])
}

// Çıktı:
// panic: runtime error: index out of range [5] with length 3

Dilim ve dizilerde indeks 0 ile len-1 arasında olmalıdır. Mesaj hem istenen indeksi hem de uzunluğu verir. Sabit bir indeks ve uzunluğu bilinen bir dizi kullanıyorsanız derleyici bu hatayı derleme sırasında yakalar; değişken indekslerde ise kontrol çalışma zamanına kalır.

go
package main

import "fmt"

type Kullanici struct{ Ad string }

func bul(id int) *Kullanici {
	if id == 1 {
		return &Kullanici{"Ali"}
	}
	return nil
}

func main() {
	u := bul(2)
	fmt.Println(u.Ad)
}

// Çıktı:
// panic: runtime error: invalid memory address or nil pointer dereference

bul fonksiyonu bulamadığında nil döndürüyor ve çağıran taraf bunu kontrol etmeden alana erişiyor. Spesifikasyona göre nil bir işaretçinin alanına erişmek çalışma zamanı panic'idir. Çıktının ikinci satırındaki [signal ...] bilgisi işletim sistemine göre değişir. Çözüm, nil dönebilen her işaretçiyi kullanmadan önce denetlemek ya da fonksiyonu (*Kullanici, bool) veya (*Kullanici, error) döndürecek biçimde tasarlamaktır. İşaretçiler için pointer rehberine bakın.

Tip denetimi, sıfıra bölme ve kanal kapatma

go
package main

import "fmt"

func main() {
	var v any = "42"
	n := v.(int)
	fmt.Println(n)
}

// Çıktı:
// panic: interface conversion: interface {} is string, not int

v.(int) biçimindeki tip denetimi (type assertion) tutmazsa panic olur. Değerin tipinden emin değilseniz comma-ok biçimini kullanın: n, ok := v.(int); tutmazsa ok false, n sıfır değer olur ve panic oluşmaz. Birden fazla tipi denemek için type switch daha okunaklıdır; type assertion ve type switch yazısına bakın.

go
package main

import "fmt"

func main() {
	a, b := 10, 0
	fmt.Println(a / b)
}

// Çıktı:
// panic: runtime error: integer divide by zero

Tamsayı bölmesinde bölen sıfırsa panic olur. Bölen sabit bir sıfırsa derleyici bunu derleme sırasında yakalar; değişkense çalışma zamanına kalır. Ondalıklı ve karmaşık sayılarda ise spesifikasyon sıfıra bölmenin sonucunu IEEE 754 standardının ötesinde tanımlamaz ve panic olup olmayacağını uygulamaya bırakır.

go
package main

func main() {
	ch := make(chan int)
	close(ch)
	close(ch)
}

// Çıktı:
// panic: close of closed channel

Bir kanalı iki kez kapatmak da panic'tir. Genel kural, kanalı yalnızca gönderen tarafın ve tek bir yerden kapatmasıdır. Kanal kuralları channel rehberinde anlatılıyor.

all goroutines are asleep - deadlock!

Bu mesaj panic değil fatal error ile başlar: çalışma zamanı, ilerleyebilecek hiçbir goroutine kalmadığını fark etmiştir ve programı kurtarılamaz biçimde durdurur. En basit hâli, tamponsuz bir kanala aynı goroutine içinde gönderim yapmaktır; gönderim, karşı tarafta bir alıcı olana kadar bekler, ama alıcı da aynı goroutine'dir.

go
package main

import "fmt"

func main() {
	ch := make(chan int)
	ch <- 1
	fmt.Println(<-ch)
}

// Çıktı:
// fatal error: all goroutines are asleep - deadlock!

Daha gerçekçi bir örnek: birkaç goroutine sonuçlarını tamponsuz bir kanala gönderiyor, main ise kanalı okumadan önce wg.Wait() ile hepsinin bitmesini bekliyor. Goroutine'ler gönderimde, main beklemede takılı kalıyor.

go
package main

import (
	"fmt"
	"sync"
)

func main() {
	var wg sync.WaitGroup
	sonuc := make(chan int)
	for i := range 3 {
		wg.Go(func() { sonuc <- i * i })
	}
	wg.Wait()
	close(sonuc)
	for v := range sonuc {
		fmt.Println(v)
	}
}

// Çıktı:
// fatal error: all goroutines are asleep - deadlock!

Burada iki düzeltme yolu var: kanalı okuma işini ayrı bir goroutine'e verip main'de okumak ya da kanala tüm sonuçları alacak kadar tampon vermek. Aşağıdaki sürüm ikinci yolu seçiyor ve sonuçlar goroutine sırasına göre farklı gelebileceği için sıralayarak yazdırıyor. WaitGroup.Go Go 1.25 ile gelen yardımcıdır; ayrıntılar sync, Mutex ve WaitGroup yazısında.

go
package main

import (
	"fmt"
	"slices"
	"sync"
)

func main() {
	var wg sync.WaitGroup
	sonuc := make(chan int, 3)
	for i := range 3 {
		wg.Go(func() { sonuc <- i * i })
	}
	wg.Wait()
	close(sonuc)

	var kareler []int
	for v := range sonuc {
		kareler = append(kareler, v)
	}
	slices.Sort(kareler)
	fmt.Println(kareler)
}

// Çıktı:
// [0 1 4]

concurrent map writes ve veri yarışları

Map'ler eşzamanlı yazma için güvenli değildir. Birden fazla goroutine aynı map'e kilitsiz yazarsa çalışma zamanı bunu çoğu zaman yakalar ve programı durdurur. Aşağıdaki programı üç kez çalıştırdık ve her seferinde fatal error: concurrent map writes mesajıyla durdu; ancak bu bir zamanlama hatası olduğu için her çalıştırmada aynı biçimde ortaya çıkması garanti değildir.

go
package main

import "sync"

func main() {
	m := map[int]int{}
	var wg sync.WaitGroup
	for i := range 100 {
		wg.Go(func() {
			for j := range 1000 {
				m[j] = i
			}
		})
	}
	wg.Wait()
}

Çözüm, map erişimini bir sync.Mutex ile korumak, özel kullanım durumları için sync.Map'i tercih etmek ya da veriyi tek bir goroutine'in sahiplendiği bir tasarım kurmaktır. Daha genel veri yarışlarını bulmak için Go'nun yarış dedektörü (go test -race, go run -race) kullanılır; resmî belgesine göre bu araç cgo gerektirir. Ayrıntılar sync/atomic ve veri yarışı yazısında.

nil olmayan nil error tuzağı

Derleyicinin de çalışma zamanının da yakalamadığı, mantıksal bir hata türü vardır. Go FAQ'da "nil error değerim neden nil'e eşit değil?" başlığıyla anlatılır: bir arayüz değeri, içindeki tip ve değer ikisi de boşsa nil'dir. Nil bir *BenimHatam işaretçisini error olarak döndürdüğünüzde arayüzün tipi doludur, bu yüzden sonuç nil değildir.

go
package main

import "fmt"

type BenimHatam struct{ Mesaj string }

func (e *BenimHatam) Error() string { return e.Mesaj }

func kontrol(bozuk bool) error {
	var p *BenimHatam
	if bozuk {
		p = &BenimHatam{"bozuk veri"}
	}
	return p // hata yokken bile nil olmayan bir error döner
}

func dogruKontrol(bozuk bool) error {
	if bozuk {
		return &BenimHatam{"bozuk veri"}
	}
	return nil
}

func main() {
	fmt.Println(kontrol(false) == nil)
	fmt.Println(dogruKontrol(false) == nil)
}

// Çıktı:
// false
// true

FAQ'nun önerisi, hata döndüren fonksiyonlarda imzada her zaman error tipini kullanmak ve başarı durumunda açıkça return nil yazmaktır. Arayüzlerin iç yapısı interface rehberinde ayrıntılı anlatılıyor.

go vet: derleyicinin kaçırdıkları

Derleyici yalnızca dilin kurallarını denetler. go vet ise derlenen ama büyük olasılıkla yanlış olan kodu bulur: ulaşılamayan kod, yanlış Printf biçim fiilleri, kopyalanan kilitler gibi. Aşağıdaki program derlenir ve çalışır, ama return'den sonraki satır hiçbir zaman çalışmaz:

go
package main

import "fmt"

func main() {
	for i := 0; i < 3; i++ {
		fmt.Println(i)
	}
	return
	fmt.Println("asla")
}
bash
go vet main.go
text
./main.go:10:2: unreachable code

go test komutu bazı vet denetimlerini otomatik olarak çalıştırır; Go 1.27 ile bunlara stdversion denetimi de eklendi. Araçların ayrıntılı kullanımı go fmt, vet ve doc yazısında.

Hata ayıklarken izlenecek sıra

Hata ayıklarken izlenecek sıra: İlk mesajı okuyun, Satır ve sütuna gidin, Panic'te yığın izini izleyin, Panic'i hata olarak ele almayın
  • İlk mesajı okuyun: Bir hata çoğu zaman ardından başka hatalar getirir. En üstteki derleme hatasını düzeltmek diğerlerini de ortadan kaldırabilir.
  • Satır ve sütuna gidin: main.go:14:16 tam olarak sorunlu ifadenin başlangıcını gösterir.
  • Panic'te yığın izini izleyin: İlk satır nedeni, sonraki satırlar hangi fonksiyon zincirinden geçildiğini söyler. Kendi paketinizdeki ilk satır genellikle aradığınız yerdir.
  • Panic'i hata olarak ele almayın: Beklenen hata durumları için error döndürün. recover yalnızca özel durumlar içindir; bkz. defer, panic ve recover.
  • Testle tekrarlayın: Hatayı üreten girdiyi bir teste dönüştürmek düzeltmenin kalıcı olmasını sağlar; test yazma yazısına bakın.

Sık Sorulan Sorular

Kullanılmayan değişken hatasını kapatan bir derleyici bayrağı var mı?

Hayır. Go FAQ böyle bir seçeneğin eklenmediğini açıkça söyler: derleyici seçenekleri dilin anlamını değiştirmemelidir ve Go derleyicisi uyarı değil yalnızca derlemeyi durduran hatalar üretir. Geçici çözüm _ = degisken atamasıdır.

panic ile fatal error arasındaki fark nedir?

panic, defer ile çalışan bir recover çağrısıyla yakalanabilir ve fonksiyonlar yukarı doğru çözülürken ertelenmiş fonksiyonlar çalışır. fatal error ile başlayan durumlarda (deadlock, eşzamanlı map yazımı) ise çalışma zamanı programı doğrudan sonlandırır. Deadlock örneğine defer içinde bir recover ekleyerek denedik: recover hiç çalışmadı ve program aynı fatal error mesajıyla durdu.

Go 1.22 öncesindeki döngü değişkeni hatası hâlâ geçerli mi?

Go 1.22 sürüm notlarına göre for döngüsünde her yineleme artık kendi değişkenini alır; closure ve goroutine'lerin hep son değeri görmesi sorunu bu sürümle giderildi. Go Blog'daki önizleme yazısına göre yeni kural yalnızca go.mod dosyasında go 1.22 veya üstünü belirten modüllerdeki paketlere uygulanır; daha eski bir go satırı olan modüllerde eski davranış sürer. Ayrıntılar for döngüsü yazısında.

Hata mesajları Go sürümüne göre değişir mi?

Evet, mesaj metinleri sürümler arasında iyileştirilebilir. Bu yazıdaki metinler Go 1.27.3 çıktısıdır. Mesajın anlamı genellikle aynı kalır; birebir metne bağlı araçlar ya da testler yazmaktan kaçının.

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 · error ve hata yönetimi · defer, panic ve recover · map kullanımı · channel ve deadlock · Go nedir?