Ruby'de birkaç alanı bir arada tutan basit bir sınıfı tek satırda oluşturmanın iki yolu vardır: Struct.new(:ad, :fiyat) değiştirilebilir, sıralanabilir (Enumerable) bir kayıt sınıfı üretir; Data.define(:ad, :fiyat) ise değiştirilemez bir değer nesnesi sınıfı üretir. Struct'ta alanlar sonradan urun.fiyat = 30 ile değişebilir ve eksik alanlar nil olur; Data'da bütün alanlar zorunludur, yazıcı metot yoktur ve değişiklik için with ile yeni bir kopya alınır.
Bu yazıda iki sınıfı, keyword_init seçeneğini, Data'nın değiştirilemezlik sınırlarını, varsayılan değer vermek için initialize ezmeyi ve ikisinin pattern matching ile kullanımını Ruby 4.0.9'da denenmiş örneklerle anlatıyoruz. Elle sınıf yazmanın ayrıntıları için Ruby class ve nesneler yazısına bakabilirsiniz.
Struct: hızlı ve değiştirilebilir kayıt sınıfı

Struct.new yeni bir sınıf döndürür; bunu bir sabite atadığınızda sınıf o adı alır. Her alan için hem okuyucu hem yazıcı metot oluşur. Ruby 3.2 NEWS dosyasına göre Struct nesneleri o sürümden beri keyword_init: true vermeden de anahtar kelime argümanlarıyla oluşturulabiliyor.
Urun = Struct.new(:ad, :fiyat)
u = Urun.new("Kalem", 25)
p u # çıktı: #<struct Urun ad="Kalem", fiyat=25>
p Urun.new(ad: "Defter", fiyat: 60) # çıktı: #<struct Urun ad="Defter", fiyat=60>
u.fiyat = 30
p u[:fiyat] # çıktı: 30
p u["ad"] # çıktı: "Kalem"
p u[1] # çıktı: 30
p u.to_a # çıktı: ["Kalem", 30]
p u.to_h # çıktı: {ad: "Kalem", fiyat: 30}
p u.members # çıktı: [:ad, :fiyat]
p u == Urun.new("Kalem", 30) # çıktı: true
p Urun.superclass # çıktı: Struct
u.each_pair { |ad, deger| puts "#{ad} = #{deger}" }
# çıktı: ad = Kalem
# çıktı: fiyat = 30Struct Enumerable modülünü içerdiği için each, map, select gibi metotlar alan değerleri üzerinde çalışır. Alanlara sembol, string ya da sıra numarasıyla [] üzerinden de erişebilirsiniz. Ruby 4.0'da to_h çıktısının {ad: "Kalem"} biçiminde gösterildiğine dikkat edin.
Eksik ve fazla argümanlar
Struct'ta argümanların hepsini vermek zorunlu değildir; verilmeyen alanlar nil olur. Alan sayısından fazla argüman ya da tanımsız bir anahtar verirseniz hata alırsınız:
Urun = Struct.new(:ad, :fiyat)
p Urun.new("Silgi") # çıktı: #<struct Urun ad="Silgi", fiyat=nil>
Urun.new("Silgi", 10, 3)
# ArgumentError: struct size differs
Urun.new(ad: "Silgi", renk: "beyaz")
# ArgumentError: unknown keywords: renkkeyword_init ile tek bir biçime zorlamak
keyword_init: true verirseniz sınıf yalnızca anahtar kelime argümanı kabul eder ve konumsal çağrı hata verir. Belgelere göre keyword_init: false ise yalnızca konumsal argüman kabul eder; bu durumda anahtar kelimeler hata vermeden tek bir hash değeri olarak ilk alana yazılır. Seçeneği hiç vermemek ya da nil vermek iki biçime de izin verir. Hangi seçeneğin kullanıldığını keyword_init? söyler (Ruby 3.1'de eklendi).
Ayar = Struct.new(:host, :port, keyword_init: true)
p Ayar.new(port: 5432, host: "db") # çıktı: #<struct Ayar host="db", port=5432>
Ayar.new("db", 5432)
# ArgumentError: wrong number of arguments (given 2, expected 0)
p Ayar.keyword_init? # çıktı: true
p Struct.new(:a).keyword_init? # çıktı: nilStruct.new bir blok da alır; blok yeni sınıfın içinde çalıştığı için ek metotlar tanımlayabilirsiniz:
Kalem = Struct.new(:ad, :adet, :birim_fiyat) do
def tutar = adet * birim_fiyat
def to_s = "#{adet} x #{ad} = #{tutar} TL"
end
k = Kalem.new("Kahve", 2, 85)
puts k # çıktı: 2 x Kahve = 170 TL
p k.tutar # çıktı: 170Data: değiştirilemez değer nesneleri
Data Ruby 3.2'de çekirdeğe eklenen bir sınıftır ve Struct'a benzer ama daha dar ve katı bir arayüz sunar. Belgeler onu değiştirilemez, atomik değerleri saklamak için tasarlanmış olarak tanımlıyor: yazıcı metot ve each gibi gezinme metotları yoktur. Kurucu hem konumsal hem anahtar kelime argümanı kabul eder ve Konum[...] kısa yazımı da çalışır.
Konum = Data.define(:enlem, :boylam)
ankara = Konum.new(39.93, 32.85)
p ankara # çıktı: #<data Konum enlem=39.93, boylam=32.85>
p Konum.new(enlem: 41.01, boylam: 28.97) # çıktı: #<data Konum enlem=41.01, boylam=28.97>
p Konum[38.42, 27.14] # çıktı: #<data Konum enlem=38.42, boylam=27.14>
p ankara.enlem # çıktı: 39.93
p ankara.to_h # çıktı: {enlem: 39.93, boylam: 32.85}
p ankara.frozen? # çıktı: true
p ankara == Konum.new(39.93, 32.85) # çıktı: true
ankara.enlem = 40
# NoMethodError: undefined method 'enlem=' for an instance of Konum
Konum.new(39.93)
# ArgumentError: missing keyword: :boylam
ankara.instance_variable_set(:@enlem, 1)
# FrozenError: can't modify frozen Konum: #<data Konum enlem=39.93, boylam=32.85>Struct'tan farklı olarak eksik alan kabul edilmez; bütün argümanlar zorunludur. Konumsal argümanlar da içeride anahtar kelimeye çevrildiği için hata mesajı missing keyword diyor. Denememizde Data nesnesi frozen? için true döndürdü; dondurulmuş nesnelerin genel davranışını frozen string ve freeze yazısında bulabilirsiniz.
with ile değiştirilmiş kopya almak
Bir alanı değiştirmek istediğinizde nesnenin kendisini değil, with ile alınmış yeni bir kopyasını kullanırsınız. Orijinal nesne olduğu gibi kalır. Tanımsız bir alan adı vermek hatadır:
kuzey = ankara.with(enlem: 40.5)
p kuzey # çıktı: #<data Konum enlem=40.5, boylam=32.85>
p ankara # çıktı: #<data Konum enlem=39.93, boylam=32.85>
ankara.with(rakim: 938)
# ArgumentError: unknown keyword: :rakimEtkinlik = Data.define(:saat, :gunler)
e = Etkinlik.new("18:00", %w[Sal Çar])
e.gunler << "Cum"
p e # çıktı: #<data Etkinlik saat="18:00", gunler=["Sal", "Çar", "Cum"]>Varsayılan değer ve dönüştürme için initialize
Data'da isteğe bağlı alan yoktur, ama initialize metodunu ezerek varsayılan değer verebilir ya da gelen değeri dönüştürebilirsiniz. Belgelerin vurguladığı ayrıntı: initialize her zaman anahtar kelime argümanı alır ve zorunlu alan kontrolü new'da değil initialize'da yapılır. Bu yüzden konumsal çağrı da aşağıdaki tanımla çalışır:
Para = Data.define(:miktar, :birim) do
def initialize(miktar:, birim: "TRY")
super(miktar: Float(miktar), birim:)
end
def to_s = format("%.2f %s", miktar, birim)
end
p Para.new(100) # çıktı: #<data Para miktar=100.0, birim="TRY">
p Para.new("49.9", "EUR") # çıktı: #<data Para miktar=49.9, birim="EUR">
puts Para.new(miktar: 12) # çıktı: 12.00 TRY
Para.new("abc")
# ArgumentError: invalid value for Float(): "abc"to_s metodunu ezdiğimiz için puts kendi biçimimizi kullandı; p ise inspect çağırdığı için varsayılan gösterim değişmedi. Ayrıca birim: yazımı, aynı adlı yerel değişkeni değer olarak geçirmenin kısa biçimidir; bu konu anahtar kelime argümanları yazısında anlatılıyor.
Pattern matching ile Struct ve Data
Her iki sınıf da deconstruct ve deconstruct_keys metotlarını sağlar. Bu sayede case/in içinde hem sınıfı hem alanları tek seferde kontrol edebilirsiniz. Alansız bir Data.define sınıfı, belgelerde de önerildiği gibi farklı sonuç türlerini temsil etmek için kullanışlıdır:
Basarili = Data.define(:govde)
Bulunamadi = Data.define
Hata = Struct.new(:kod, :mesaj)
def ozetle(yanit)
case yanit
in Basarili(govde:) if govde.empty?
"boş yanıt"
in Basarili(govde)
"içerik: #{govde}"
in Bulunamadi
"404"
in Hata[kod, mesaj]
"hata #{kod}: #{mesaj}"
end
end
p ozetle(Basarili.new("<html>")) # çıktı: "içerik: <html>"
p ozetle(Basarili.new("")) # çıktı: "boş yanıt"
p ozetle(Bulunamadi.new) # çıktı: "404"
p ozetle(Hata.new(500, "sunucu")) # çıktı: "hata 500: sunucu"
p Basarili.new("x").deconstruct_keys(nil) # çıktı: {govde: "x"}Basarili(govde) konumsal desen deconstruct'ı, Basarili(govde:) ise deconstruct_keys'i kullanır. Desen sözdiziminin tamamı için Ruby pattern matching yazısına bakın.
Eşitlik ve hash anahtarı olarak kullanım
Data nesneleri aynı sınıftan ve aynı içerikteyse == ile eşittir ve aynı hash değerini üretir; bu yüzden hash anahtarı olarak güvenle kullanılabilir. eql? ise alanları da eql? ile karşılaştırdığı için 1 ile 1.0 farklı sayılır. Aynı alanlara sahip olsa bile farklı Data sınıflarının nesneleri hiçbir zaman eşit değildir.
Konum = Data.define(:enlem, :boylam)
ziyaret = Hash.new(0)
ziyaret[Konum[39.93, 32.85]] += 1
ziyaret[Konum[39.93, 32.85]] += 1
p ziyaret # çıktı: {#<data Konum enlem=39.93, boylam=32.85> => 2}
p Konum[1, 2] == Konum[1.0, 2] # çıktı: true
p Konum[1, 2].eql?(Konum[1.0, 2]) # çıktı: false
Diger = Data.define(:enlem, :boylam)
p Konum[1, 2] == Diger[1, 2] # çıktı: falseStruct mı, Data mı, sınıf mı?

| Özellik | Struct | Data | Elle yazılan sınıf |
|---|---|---|---|
| Alan değiştirme | Evet (yazıcılar var) | Hayır, with ile kopya | Siz belirlersiniz |
| Eksik argüman | nil olur | ArgumentError | Siz belirlersiniz |
| Enumerable | Evet | Hayır | İsterseniz include edersiniz |
| == ve hash | İçeriğe göre | İçeriğe göre | Varsayılan olarak nesne kimliği |
| Pattern matching | deconstruct ve deconstruct_keys var | deconstruct ve deconstruct_keys var | Kendiniz tanımlarsınız |
| Uygun olduğu yer | Değişen kayıtlar, geçici gruplama | Değer nesneleri, sonuç türleri | Davranışı ağır nesneler |
Kısaca: bir nesnenin kimliği değil yalnızca değeri önemliyse ve oluşturulduktan sonra değişmemesi gerekiyorsa Data; alanların sonradan değişeceği hafif bir kayıt gerekiyorsa Struct; karmaşık davranış ve iç durum yönetimi gerekiyorsa normal sınıf seçin. Basit anahtar-değer verisi için Hash de yeterli olabilir.
Sık Sorulan Sorular
Data hangi sürümde geldi?
Ruby 3.2 NEWS dosyası Data'yı yeni çekirdek sınıf olarak listeliyor. Ruby 4.0 NEWS dosyasında Data veya Struct için öne çıkan bir değişiklik yer almıyor.
Struct.new'e string ad verirsem ne olur?
Belgelere göre ilk argüman bir string ise sınıf Struct::Ad adıyla Struct'ın altında oluşturulur. Genellikle sembollerle çağırıp sonucu kendi sabitinize atamak daha okunur bir ad verir.
Data nesnesine yeni metot ekleyebilir miyim?
Evet. Data.define bir blok alır ve blok yeni sınıfın içinde çalışır; Comparable gibi modülleri de burada ekleyebilirsiniz. Örnek için Comparable ve Enumerable yazısına bakın.
Struct'ı dondurabilir miyim?
freeze her nesnede olduğu gibi Struct'ta da çalışır, ama Data değiştirilemezliği sınıfın tasarımına yerleştirdiği için niyetinizi daha açık ifade eder.
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: Ruby öğrenme rehberi · Ruby class ve nesneler · Ruby pattern matching · frozen string ve freeze · anahtar kelime argümanları · Ruby Hash metotları