Göktay Logo

Artık Uzun Struct Literalleri Yazmıyorum: Go 1.27 ve Keyed Fields

5 dk okuma

Go ile yıllardır kod yazıyorum ve dilin sadeliğini seviyorum. Ama tek bir konuda her seferinde içimden “bunun daha iyi bir yolu olmalı” demişimdir: anonim gömme (embedding) kullanan struct’ları başlatmak. İç içe geçmiş, tekrar tekrar tip adı yazdığımız o uzun literal’ler, Go’nun ilk gününden beri gözüme batan bir gürültüydü.

Go 1.27 ile bu gürültü sonunda bitiyor. keyed fields denilen küçük ama etkili bir sözdizimi değişikliği, terfi eden (promoted) alanları doğrudan kullanmamıza izin veriyor. Gelin neden bu kadar sevindiğimi örneklerle görelim.

Sorun: Neden Bu Kadar Çok Satır Yazıyorduk?

Go’da kompozisyon, kalıtımın (inheritance) yerini alır. Bir tipi başka bir struct’ın içine anonim olarak gömdüğümüzde, onun alanları dış struct’a “terfi eder” ve doğrudan p.CreatedAt gibi erişilebilir hale gelir. Bu, Clean Architecture yazımda bahsettiğim kompozisyon felsefesinin ta kendisidir.

Diyelim ki bir domain modelimiz var; ortak alanları küçük, odaklı tiplere ayırmışız:

type Timestamps struct {
    CreatedAt time.Time
    UpdatedAt time.Time
}

type Audit struct {
    CreatedBy string
    UpdatedBy string
}

type Product struct {
    Name  string
    Price int
    Timestamps // anonim gömme
    Audit      // anonim gömme
}

Erişimde her şey harika: product.CreatedAt ya da product.CreatedBy diyebiliyoruz, sanki hepsi tek bir düz struct’mış gibi. Ama iş bu struct’ı başlatmaya gelince Go 1.27’den önce şöyle bir manzarayla karşılaşıyorduk:

p := Product{
    Name:  "Mekanik Klavye",
    Price: 1200,
    Timestamps: Timestamps{
        CreatedAt: now,
        UpdatedAt: now,
    },
    Audit: Audit{
        CreatedBy: "goktay",
        UpdatedBy: "goktay",
    },
}

Fark ettiniz mi? Erişirken düz olan yapı, başlatırken aniden iç içe geçiyor. Timestamps ve Audit adlarını tekrar yazmak, süslü parantezleri iç içe açmak zorundayız. Okurken kafamda hep şu soru belirirdi: “Madem alanlara düz erişiyorum, neden düz atayamıyorum?”

Go 1.27 Çözümü: Terfi Eden Alanlar Artık Anahtar Olabiliyor

Go 1.27, tam da bunu çözüyor. Terfi eden alanları, gömülü tipin adını hiç anmadan doğrudan literal içinde anahtar olarak yazabiliyoruz:

p := Product{
    Name:      "Mekanik Klavye",
    Price:     1200,
    CreatedAt: now,      // Timestamps'ten terfi etti
    UpdatedAt: now,      // Timestamps'ten terfi etti
    CreatedBy: "goktay", // Audit'ten terfi etti
    UpdatedBy: "goktay", // Audit'ten terfi etti
}

İç içe süslü parantez yok, tekrar eden tip adı yok. Erişimdeki düzlük, artık başlatmada da geçerli. Bu, 12 satırlık gürültüyü 8 satırlık berrak bir yapıya indiriyor — üstelik hiçbir anlam kaybı olmadan.

Production Notu: Bu bir sözdizimi kolaylığı; altında yatan bellek düzeni (memory layout) veya performans hiç değişmiyor. Derleyici hangi alanın hangi gömülü tipe ait olduğunu zaten biliyor. Yani bu değişiklik “sıfır maliyetli” bir okunabilirlik kazanımıdır.

Gerçek Dünya: Konfigürasyon Struct’ları

Bu özelliğin gerçek değerini, günlük hayatta en çok gömme kullandığımız yerde görüyoruz: konfigürasyon. Yüksek trafikli bir serviste tipik bir ServerConfig, alt konfigürasyonları gömerek oluşturulur:

type DatabaseConfig struct {
    Host     string
    Port     int
    Name     string
    MaxConns int
}

type RedisConfig struct {
    Addr string
    TTL  int
}

type ServerConfig struct {
    DatabaseConfig // anonim gömme
    RedisConfig    // anonim gömme
}

Eski usul, her gömülü tipi ayrı ayrı iç içe başlatmayı gerektiriyordu:

cfg := ServerConfig{
    DatabaseConfig: DatabaseConfig{
        Host:     "localhost",
        Port:     5432,
        Name:     "betco",
        MaxConns: 100,
    },
    RedisConfig: RedisConfig{
        Addr: "localhost:6379",
        TTL:  300,
    },
}

Go 1.27 ile aynı yapı, düz ve okunması kolay bir hale geliyor:

cfg := ServerConfig{
    Host:     "localhost", // DatabaseConfig
    Port:     5432,        // DatabaseConfig
    Name:     "betco",     // DatabaseConfig
    MaxConns: 100,         // DatabaseConfig
    Addr:     "localhost:6379", // RedisConfig
    TTL:      300,              // RedisConfig
}

Mühendislik Notu: Bu tür domain ve config struct’larını sıfırdan elle yazmak yerine, JSON veya SQL şemanızdan otomatik üretmek isterseniz hazırladığım Golang Struct Generator aracını kullanabilirsiniz. Karmaşık bir API yanıtından struct çıkarmak için de Advanced JSON Formatter iş görür.

Asıl Güç: Çok Katmanlı Terfi

Özelliğin beni en çok etkileyen yanı, terfinin birden fazla katmanda çalışması. Bir tipi, başka bir gömülü tipin içinden geçerek en dışa terfi ettirebiliyorsunuz:

type Coordinates struct {
    Lat float64
    Lng float64
}

type Location struct {
    Coordinates // anonim gömme
    Altitude    float64
}

type Warehouse struct {
    Name     string
    Location // iki kat derinlikte anonim gömme
}

Go 1.26’da bir Warehouse başlatmak, üç kat iç içe süslü parantez demekti. Go 1.27’de ise Lat, Lng ve Altitude — hepsi iki katman üzerinden terfi ederek — doğrudan anahtar oluyor:

w := Warehouse{
    Name: "Ana Depo",
    Lat:  40.7128,
    Lng:  -74.0060,
    Altitude: 10.5,
}

14 satırlık iç içe yapı, 6 satırlık düz bir bloğa iniyor. Derin gömme kullanan büyük yapılarda bu fark katlanarak büyüyor.

Ne Zaman Kullanmalı, Ne Zaman Kaçınmalı?

Her yeni özellik gibi bunu da körlemesine kullanmak yerine bilinçli tercih etmek gerekir.

Şu durumda sorunsuz çalışır: Terfi eden alan adı benzersiz olduğunda. Yani iki farklı gömülü tip aynı isimde bir alan terfi ettirmiyorsa.

Şu durumda derleyici sizi durdurur: Eğer iki gömülü tip de örneğin ID adında bir alan terfi ettiriyorsa, ID: yazdığınızda derleyici “hangisi?” diye hata verir. Bu durumda o alan için eski, açık iç içe sözdizimine geri dönersiniz. Bu aslında iyi bir şey — belirsizliği sessizce yutmak yerine derleme aşamasında yakalar.

Production Notu: Gömülü tip çok büyükse (örneğin 30 alanlı bir config) ve siz sadece 3 alanını dolduruyorsanız, bazen eski iç içe sözdizimi daha okunaklı olabilir; çünkü hangi alanın hangi gruba ait olduğunu görsel olarak belli eder. Kısacası: keyed fields’ı varsayılanınız yapın, ama gruplamayı vurgulamak istediğiniz karmaşık yapılarda iç içe sözdizimini bilinçli olarak koruyun.

Sonuç

Go 1.27’nin keyed fields özelliği, manşetlere çıkacak türden gösterişli bir yenilik değil. Ama tam da bu yüzden Go’nun ruhuna uygun: sessizce araya girip yıllardır katlandığımız bir sıkıntıyı ortadan kaldırıyor. Go’da struct gömme neredeyse her yerde olduğuna göre — domain modellerinden config’lere kadar — bu küçük değişikliğin faydasını daha ilk go build komutunda hissedeceksiniz.

Ben şimdiden yeni yazdığım her struct literal’ini bu sözdizimiyle yazmaya başladım bile. Dilin sizin için çalışması, sizin dil için çalışmanızdan her zaman iyidir.