Go dilinde struct, her birinin adı ve tipi olan alanlardan oluşan bileşik bir tiptir. type Kullanici struct { Ad string; Yas int } ile tanımlanır, Kullanici{Ad: "Ayşe", Yas: 31} gibi bir bileşik literal (composite literal) ile oluşturulur ve alanlara nokta ile erişilir. Go'da sınıf ve kalıtım yoktur; struct'lar metotlar, arayüzler ve gömme (embedding) ile birleşerek aynı ihtiyaçları karşılar. Struct bir değer tipidir: atandığında ya da fonksiyona verildiğinde tüm alanları kopyalanır.
Bu yazıda struct tanımlamayı, konumsal ve anahtarlı literalleri, sıfır değerleri, işaretçi üzerinden alan erişimini, anonim struct'ları, gömülü alanları ve terfi eden (promoted) alanları, Go 1.27 ile gelen alan seçici anahtarları, karşılaştırılabilirliği, alan etiketlerini ve bellek düzenini Go 1.27.3 ile çalıştırılmış örneklerle anlatıyoruz. Metot tanımlama ayrıntıları için Go metotlar ve alıcı tipleri yazısına, serinin tamamı için Go öğrenme rehberi sayfasına bakabilirsiniz.
Struct tanımlama ve bileşik literaller

Spesifikasyon struct'ı, alan adı verilmiş elemanlar dizisi olarak tanımlar; bir struct içindeki boş olmayan alan adları benzersiz olmalıdır. Bir struct değeri oluşturmanın üç yolu vardır: değişkeni tanımlayıp sıfır değerini kullanmak, konumsal literal yazmak ya da anahtarlı (alan adlı) literal yazmak.
package main
import "fmt"
type Kullanici struct {
Ad string
Yas int
Aktif bool
}
func main() {
var bos Kullanici // tüm alanlar sıfır değer
fmt.Printf("%+v\n", bos)
a := Kullanici{"Ayşe", 31, true} // konumsal: tüm alanlar, sırayla
b := Kullanici{Ad: "Mert", Aktif: true} // anahtarlı: Yas sıfır kalır
fmt.Printf("%+v\n%+v\n", a, b)
p := &b
p.Yas = 28 // (*p).Yas yerine kısa yazım
fmt.Println(b.Yas)
nokta := struct{ X, Y int }{3, 4} // anonim struct
fmt.Println(nokta, nokta.X*nokta.Y)
}
// Çıktı:
// {Ad: Yas:0 Aktif:false}
// {Ad:Ayşe Yas:31 Aktif:true}
// {Ad:Mert Yas:0 Aktif:true}
// 28
// {3 4} 12Anahtarsız literalde her alan için bir değer, tanım sırasıyla verilmelidir. Anahtarlı literalde ise alanlar istediğiniz sırada yazılabilir ve verilmeyen alanlar sıfır değer alır. Effective Go, eleman listesi tamamen boş olan bir literalin tipin sıfır değerini ürettiğini ve new(File) ile &File{} ifadelerinin eşdeğer olduğunu belirtir. Başka paketlerin struct'larında konumsal literal kullanmak risklidir: paketin yeni bir alan eklemesi kodunuzu derlenmez hale getirir. Bu yüzden dışarıdan gelen tiplerde anahtarlı literal tercih edilir.
%+v fiili alan adlarıyla birlikte yazdırır ve hata ayıklamada kullanışlıdır; tüm fiiller için Go fmt ve Printf yazısına bakın. İşaretçi üzerinden alan erişiminde (*p).Yas yazmanıza gerek yoktur: Tour of Go'nun anlattığı gibi dil p.Yas kısaltmasına izin verir. Spesifikasyonun seçici kuralı da bunu tanımlar: x bir işaretçi tipindeyse ve (*x).f geçerli bir alan seçiciyse x.f onun kısaltmasıdır. İşaretçi nil ise bu alanı okumak ya da yazmak çalışma anında panik üretir.
Anonim struct'lar, tek seferlik bir veri yapısı için tip adı tanımlamadan kullanılır; test tablolarında ve geçici JSON gövdelerinde sık görülür. Effective Go, yerel bir değişkenin adresini döndürmenin C'den farklı olarak tamamen güvenli olduğunu, değişkenin depolama alanının fonksiyon döndükten sonra da yaşadığını belirtir; bu yüzden kurucu fonksiyonlar genellikle return &T{...} biçiminde yazılır.
Gömme (embedding) ve terfi eden alanlar
Bir alanı yalnızca tip adıyla, alan adı vermeden tanımlarsanız bu bir gömülü alan olur. Spesifikasyona göre gömülü alan T ya da *T biçiminde bir tip adı olmalıdır ve nitelenmemiş tip adı alan adı görevi görür. Gömülü alanın alanları ve metotları dış struct'a terfi eder: m.Adres.Sehir yerine m.Sehir yazabilirsiniz.
package main
import "fmt"
type Adres struct {
Sehir string
}
func (a Adres) Etiket() string { return "Şehir: " + a.Sehir }
type Musteri struct {
Ad string
Adres
}
type Sube struct {
Sehir string // gömülü alandaki Sehir'i gölgeler
Adres
}
func main() {
m := Musteri{Ad: "Can", Adres: Adres{Sehir: "Bursa"}}
fmt.Println(m.Sehir, m.Adres.Sehir) // terfi eden alan
fmt.Println(m.Etiket()) // terfi eden metot
s := Sube{Sehir: "Merkez", Adres: Adres{Sehir: "İzmir"}}
fmt.Println(s.Sehir, s.Adres.Sehir, s.Etiket())
}
// Çıktı:
// Bursa Bursa
// Şehir: Bursa
// Merkez İzmir Şehir: İzmirEffective Go, gömmenin alt sınıflamadan önemli bir farkını vurgular: gömülü tipin metotları dış tipin metotları olur, ama çağrıldıklarında alıcı (receiver) dış tip değil iç tiptir. m.Etiket() çağrısı aslında m.Adres.Etiket() çağrısıdır ve Etiket metodu Musteri hakkında hiçbir şey bilmez.
İsim çakışmalarında kural basittir: daha sığ derinlikteki bir alan ya da metot, daha derindeki aynı adlı öğeyi gölgeler. Sube struct'ındaki Sehir alanı derinlik sıfırda olduğu için s.Sehir onu seçer; gömülü Adres içindeki alana ancak s.Adres.Sehir ile ulaşırsınız. Spesifikasyona göre aynı derinlikte birden fazla aday varsa seçici geçersizdir. Effective Go, aynı derinlikte çakışan bir ad tip tanımının dışında programda hiç kullanılmıyorsa bunun hata sayılmadığını da ekler; bu kural, dışarıdan gömülen tiplere sonradan alan eklendiğinde kodunuzun kırılmasını önler. Gömülü tiplerin metotlarının metot kümelerine nasıl katıldığı ve arayüz uygulaması için Go metotlar ve Go interface yazılarına bakın.
Go 1.27: struct literalinde alan seçici anahtarlar

Go 1.27 sürüm notlarına göre bir struct literalindeki anahtar artık yalnızca struct'ın üst düzey alan adı değil, struct tipi için geçerli herhangi bir alan seçici olabilir. Pratikte bu, terfi eden bir alanı gömülü struct'ı ayrıca yazmadan doğrudan anahtar olarak kullanabileceğiniz anlamına gelir:
package main
import "fmt"
type Nesne struct{ Ad, Renk string }
type Nokta struct {
Nesne
X, Y int
}
func main() {
// Go 1.27: anahtar, terfi eden bir alan da olabilir.
n := Nokta{Ad: "köşe", X: 1, Y: 2}
fmt.Printf("%+v\n", n)
// Go 1.27 öncesi yazım hâlâ geçerli:
m := Nokta{Nesne: Nesne{Ad: "köşe"}, X: 1, Y: 2}
fmt.Println(n == m)
}
// Çıktı:
// {Nesne:{Ad:köşe Renk:} X:1 Y:2}
// trueSpesifikasyonun güncel struct literal kuralları bu özelliğe bazı sınırlar koyar. Hedef alana ulaşmak için geçilen gömülü alanların tipi işaretçi olmamalıdır; yani *Nesne gömülü olsaydı Ad anahtarı kullanılamazdı. Ayrıca aynı gömülü struct başka bir anahtarla da belirtilmişse, onun içindeki terfi eden alan anahtar olarak kullanılamaz. Örtüşen anahtarlar derleme hatası verir:
package main
import "fmt"
type Nesne struct{ Ad, Renk string }
type Nokta struct {
Nesne
X int
}
func main() {
n := Nokta{Nesne: Nesne{Ad: "a"}, Ad: "b"}
fmt.Println(n)
}# command-line-arguments
./main.go:13:36: cannot specify promoted field Ad and enclosing embedded field NesneÖzellik dil sürümüne bağlıdır. Modülünüzün go.mod dosyasında go 1.26 yazıyorsa Go 1.27.3 derleyicisi bile bu yazımı reddeder ve şu mesajı verir (denediğimiz modülde):
./main.go:14:13: use of promoted field Nesne.Ad in struct literal of type Nokta requires go1.27 or later (-lang was set to go1.26; check go.mod)go yönergesinin dil sürümünü nasıl belirlediği Go mod modül yönetimi yazısında, sürümün diğer yenilikleri Go 1.27 yenilikleri yazısında anlatılıyor.
Karşılaştırma, kopyalama ve map anahtarı
Spesifikasyona göre bir struct tipi, tüm alan tipleri karşılaştırılabiliyorsa karşılaştırılabilirdir. İki struct değeri, boş olmayan karşılık gelen alanları eşitse eşittir; alanlar kaynak sırasıyla karşılaştırılır ve ilk farkta durulur. Karşılaştırılabilir struct'lar map anahtarı olarak kullanılabilir. Atama ise struct'ı kopyalar, ama kopya sığdır: struct içindeki dilim, map ya da işaretçi alanları aynı veriyi göstermeye devam eder.
package main
import "fmt"
type Para struct {
Tutar int64
Birim string
}
type Sepet struct {
Urunler []string
}
func main() {
a := Para{100, "TRY"}
b := Para{100, "TRY"}
fmt.Println(a == b, a != Para{100, "EUR"})
toplam := map[Para]int{a: 1}
toplam[b]++
fmt.Println(toplam[Para{100, "TRY"}])
s1 := Sepet{Urunler: []string{"çay"}}
s2 := s1 // struct kopyalanır, ama dilim aynı diziyi paylaşır
s2.Urunler[0] = "su" // s1 de etkilenir
fmt.Println(s1.Urunler, s2.Urunler)
}
// Çıktı:
// true true
// 2
// [su] [su]s2 := s1 satırı yeni bir Sepet değeri oluşturur; ancak Urunler alanı bir dilim başlığı olduğu için iki struct aynı alttaki diziyi paylaşır ve s2.Urunler[0] ataması s1 tarafında da görünür. Derin kopya gerekiyorsa dilim ve map alanlarını ayrıca kopyalamanız gerekir (slices.Clone, maps.Clone). Dilim içeren bir struct ise == ile karşılaştırılamaz:
package main
type Sepet struct {
Urunler []string
}
func main() {
a, b := Sepet{}, Sepet{}
_ = a == b
}# command-line-arguments
./main.go:9:6: invalid operation: a == b (struct containing []string cannot be compared)Arayüz tipli alanlar özel bir durum yaratır: spesifikasyona göre aynı dinamik tipe sahip iki arayüz değerinin karşılaştırılması, o tip karşılaştırılamıyorsa çalışma anında paniğe yol açar ve bu kural arayüz alanı içeren struct'lar için de geçerlidir. Yani böyle bir struct derlenir ama karşılaştırma çalışma anında başarısız olabilir.
Boş struct ve bellek düzeni
Alanı olmayan struct{} tipinin boyutu spesifikasyona göre sıfırdır; bu yüzden küme olarak kullanılan map'lerde ve yalnızca sinyal taşıyan kanallarda değer tipi olarak sık kullanılır. Alanların bellekteki sırası ise hizalama (alignment) nedeniyle struct boyutunu etkileyebilir. unsafe.Sizeof ile bunu görebilirsiniz:
package main
import (
"fmt"
"unsafe"
)
type Bayrak struct{}
type Kayit struct {
A bool
B int64
C bool
}
type Sikisik struct {
B int64
A bool
C bool
}
func main() {
fmt.Println(unsafe.Sizeof(Bayrak{}))
fmt.Println(unsafe.Sizeof(Kayit{}), unsafe.Sizeof(Sikisik{}))
}
// Go 1.27.3 windows/amd64 ile alınan çıktı
// (boyutlar mimariye bağlıdır):
// 0
// 24 16Aynı üç alan iki farklı sırayla yazıldığında boyut değişti, çünkü bu platformda int64 alanı 8 baytlık sınıra hizalanır ve bool alanlarının arasına dolgu (padding) eklenir. Spesifikasyon yalnızca asgari hizalama kurallarını garanti eder; bir struct'ın hizalaması, alanlarının hizalamalarının en büyüğüdür. Boyutlar mimariye ve derleyiciye bağlıdır; çıktı yukarıdaki platformda alınmıştır. Çoğu programda bu ayrıntı önemli değildir; milyonlarca eleman tutan dilimlerde bellek kullanımını düşürmek gerektiğinde değerlendirilir. unsafe paketinin kullanımı taşınabilirlik garantilerinin dışındadır.
Struct özellikleri özeti
| Konu | Kural | Not |
|---|---|---|
| Sıfır değer | Her alan kendi sıfır değerini alır | Ek başlatma gerekmeden kullanılabilir |
| Konumsal literal | Tüm alanlar, tanım sırasıyla | Dış paket tiplerinde kırılgan |
| Anahtarlı literal | Alan adı: değer, sıra serbest | Verilmeyen alan sıfır kalır |
| Gömülü alan | Tip adı alan adı olur | Alan ve metotlar terfi eder |
| Terfi eden alan anahtarı | Go 1.27 ve sonrası | İşaretçi gömmeden geçilemez |
| == karşılaştırması | Tüm alanlar karşılaştırılabilirse | Dilim, map, fonksiyon alanı engeller |
| Atama | Alanlar kopyalanır (sığ) | Dilim ve map alanları paylaşılır |
| Etiket | Alandan sonra string literal | reflect ile okunur |
Sık yapılan hatalar

- Değer kopyasını değiştirmek: struct'ı fonksiyona değerle verip alanını değiştirmek çağıranı etkilemez; işaretçi verin ya da yeni değeri döndürün.
- Sığ kopyayı derin sanmak: struct atamasında dilim ve map alanları paylaşılmaya devam eder.
- Dış paket struct'ında konumsal literal: pakete alan eklendiğinde kod kırılır; anahtarlı literal kullanın.
- Küçük harfli alanın JSON'a girmesini beklemek: dışa aktarılmayan alanlar kodlama paketleri tarafından görülmez.
- nil struct işaretçisinde alan okumak:
var p *Tsonrasıp.Alanpanikler. - Terfi eden alanı eski dil sürümünde anahtar yapmak:
go.modiçindekigoyönergesi 1.27'den düşükse derleme hatası alırsınız. - Map içindeki struct alanını doğrudan değiştirmek:
m[k].Alan = vderlenmez; Go map yazısındaki çözümlere bakın.
Sık Sorulan Sorular
Go'da struct bir sınıf mıdır?
Hayır. Go'da sınıf ve kalıtım yoktur. Struct yalnızca veri alanlarını tanımlar; davranış metotlarla, soyutlama arayüzlerle, kod yeniden kullanımı gömme ile sağlanır. Gömme bir alt sınıflama değildir: terfi eden metodun alıcısı her zaman iç tiptir.
Struct'ı değer olarak mı işaretçi olarak mı geçirmeliyim?
Fonksiyonun struct'ı değiştirmesi gerekiyorsa işaretçi geçirin. Go SSS, büyük struct'lar için işaretçinin daha ucuz olabileceğini, küçük struct'larda ise değer geçirmenin verimli ve açık olduğunu belirtir. Ayrıntılı tartışma için Go pointer yazısına bakın.
İki struct'ı nasıl karşılaştırırım?
Tüm alanlar karşılaştırılabiliyorsa doğrudan == kullanın. Dilim veya map alanı varsa == derlenmez; alanları kendiniz karşılaştıran bir fonksiyon yazmanız ya da slices.Equal ve maps.Equal gibi yardımcıları kullanmanız gerekir.
Anonim struct ne zaman kullanılır?
Bir veri yapısına yalnızca tek bir yerde ihtiyaç varsa: tablo tabanlı testlerde test durumlarını listelemek, geçici bir JSON gövdesi oluşturmak ya da yerel bir gruplama yapmak için. Aynı yapı birden fazla yerde kullanılıyorsa adlandırılmış bir tip tanımlamak daha okunaklıdır. Tablo testleri için Go test yazma yazısına bakabilirsiniz.
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 metotlar ve alıcı tipleri · Go interface · Go pointer · Go encoding/json · Go 1.27 yenilikleri