Ruby'de freeze bir nesneyi kalıcı olarak değiştirilemez hale getirir; dondurulmuş bir nesneyi değiştirmeye çalışırsanız FrozenError alırsınız ve nesneyi tekrar çözmenin bir yolu yoktur. frozen? durumu sorgular, dup ve tekli artı (+str) değiştirilebilir bir kopya verir. Dosyanın başına yazılan # frozen_string_literal: true yorumu o dosyadaki tüm metin literallerini dondurur. Ruby 4.0'da bu yorum yoksa literaller hâlâ değiştirilebilir; Ruby 3.4'ten beri yalnızca kullanımdan kaldırma uyarıları açıksa bir uyarı verilir.

Bu yazıda dondurmanın neyi kapsayıp neyi kapsamadığını, dup ile clone farkını, metinlere özel +@ ve -@ operatörlerini, sihirli yorumun davranışını ve Ruby 4.0 NEWS dosyasının bu konuda gerçekte ne söylediğini çalıştırılmış örneklerle ele alıyoruz. Metin metotlarının geneli için Ruby string metotları, Ruby'ye genel bakış için Ruby öğrenme rehberi sayfasına bakabilirsiniz.

freeze, frozen? ve FrozenError

freeze, frozen? ve FrozenError

Object#freeze nesnenin kendisini döndürür ve o andan sonra nesneyi değiştiren her işlem FrozenError fırlatır. Belgelere göre FrozenError sınıfının receiver metodu, değiştirilmeye çalışılan nesneyi verir; hatayı ayıklarken hangi nesnenin dondurulmuş olduğunu bulmak için kullanışlıdır. FrozenError, RuntimeError sınıfının alt sınıfıdır.

ruby
meyveler = ["elma", "armut"].freeze
p meyveler.frozen?
begin
  meyveler << "kiraz"
rescue FrozenError => e
  puts e.class
  puts e.message
  p e.receiver.equal?(meyveler)
end
# çıktı: true
# çıktı: FrozenError
# çıktı: can't modify frozen Array: ["elma", "armut"]
# çıktı: true

Bazı nesneler zaten donmuş gelir. Belgeler Integer, Float ve Symbol nesnelerinin her zaman donmuş olduğunu belirtir. Aşağıdaki çıktıda nil de donmuş görünürken, sihirli yorum olmayan bir dosyadaki metin literali donmuş değildir:

ruby
p 42.frozen?
p 3.14.frozen?
p :durum.frozen?
p nil.frozen?
p "metin".frozen?
# çıktı: true
# çıktı: true
# çıktı: true
# çıktı: true
# çıktı: false

Dondurma yüzeyseldir

freeze yalnızca çağrıldığı nesneyi dondurur, içindeki nesneleri değil. Donmuş bir hash'e yeni anahtar ekleyemezsiniz ama değeri olan diziye eleman ekleyebilirsiniz. Sabitleri "tamamen değişmez" yapmak istiyorsanız iç nesneleri de ayrıca dondurmanız gerekir.

ruby
AYARLAR = {renk: "mavi", diller: ["tr", "en"]}.freeze

AYARLAR[:diller] << "de"
p AYARLAR
p AYARLAR[:diller].frozen?

begin
  AYARLAR[:yeni] = 1
rescue FrozenError => e
  puts e.message
end
# çıktı: {renk: "mavi", diller: ["tr", "en", "de"]}
# çıktı: false
# çıktı: can't modify frozen Hash: {renk: "mavi", diller: ["tr", "en", "de"]}

İç içe yapıları dondurmak için basit bir özyinelemeli yardımcı yazabilirsiniz:

ruby
def derin_dondur(nesne)
  case nesne
  when Hash then nesne.each_value { derin_dondur(it) }
  when Array then nesne.each { derin_dondur(it) }
  end
  nesne.freeze
end

ayar = derin_dondur({renk: "mavi", diller: ["tr", "en"]})
p ayar[:diller].frozen?
p ayar[:diller][0].frozen?
# çıktı: true
# çıktı: true

dup ve clone farkı

İki metot da yüzeysel bir kopya üretir, ama donmuşluk durumunu farklı ele alır. Belgelere göre clone nesnenin donmuş durumunu kopyalar; freeze: anahtar kelimesiyle bunu açıkça belirleyebilirsiniz. dup ise donmuş durumu taşımaz ve ayrıca nesneye extend ile eklenmiş modülleri kopyalamaz. Donmuş bir nesnenin değiştirilebilir kopyasına ihtiyacınız varsa dup kullanın.

ruby
asil = "sabit".freeze
p asil.dup.frozen?
p asil.clone.frozen?
p asil.clone(freeze: false).frozen?
# çıktı: false
# çıktı: true
# çıktı: false

Metinlere özel: +str ve -str

Metinlere özel: +str ve -str

String sınıfı dondurmayla ilgili iki tekli operatör sunar. +str (+@), metin donmuş değilse ve uyarı vermeden değiştirilebiliyorsa metnin kendisini, aksi halde donmamış bir kopyasını döndürür. -str (-@, takma adı dedup) ise donmuş bir metin döndürür ve mümkün olduğunda aynı içerikli metinleri tek bir nesnede birleştirir. Belgeler bunu veri tekilleştirme (deduplication) olarak açıklar: aynı metin binlerce kez bellekte tutuluyorsa tek kopya yeterli olur.

ruby
a = "merhaba".freeze
b = +a
b << " dünya"
p b
p a
p b.equal?(a)

x = -"ortak"
y = -"ortak"
p x.frozen?
p x.equal?(y)
# çıktı: "merhaba dünya"
# çıktı: "merhaba"
# çıktı: false
# çıktı: true
# çıktı: true

Ruby 3.4 NEWS dosyasına göre +str, değiştirildiğinde kullanımdan kaldırma uyarısı üretecek bir metinde de kopya döndürür; NEWS bunu str.dup if str.frozen? kalıbının yerine önerir. Yani bir metni değiştirmeden önce + ile almak, hem donmuş hem de ileride donacak literaller için güvenli bir alışkanlıktır.

frozen_string_literal sihirli yorumu

Belgelere göre dosyanın ilk yorum bölümüne yazılan # frozen_string_literal: true, o dosyadaki metin literallerinin ayrıştırma sırasında bir kez oluşturulup dondurulmasını sağlar. Aynı literal döngüde tekrar tekrar çalışsa bile hep aynı nesne kullanılır. Ruby 3.0'dan beri ara değer içeren ("#{...}") dinamik literaller bu yorumdan etkilenmez; donmaz ve paylaşılmaz.

ruby
# frozen_string_literal: true

s = "merhaba"
p s.frozen?
begin
  s << "!"
rescue FrozenError => e
  puts e.message
end

p 3.times.map { "sabit".object_id }.uniq.size
p "Toplam: #{1 + 1}".frozen?

t = +"merhaba"
t << "!"
p t
p String.new("abc").frozen?
# çıktı: true
# çıktı: can't modify frozen String: "merhaba"
# çıktı: 1
# çıktı: false
# çıktı: "merhaba!"
# çıktı: false

Yorum açıkken değiştirilebilir bir metne ihtiyaç duyduğunuzda +"...", dup veya String.new kullanabilirsiniz. Dikkat: belgelere göre argümansız String.new ASCII-8BIT kodlamalı bir metin üretir; metin argümanı verirseniz o metnin kodlamasını alır. Kodlama konusunun ayrıntısı Ruby encoding ve Türkçe karakter yazısında.

Ruby 3.4 ve 4.0'da durum ne?

Bu konu sık sık yanlış aktarıldığı için NEWS dosyalarının gerçekte ne söylediğine bakalım. Ruby 3.4 NEWS dosyası şunu listeler: frozen_string_literal yorumu olmayan dosyalarda metin literalleri değiştirildiğinde bir kullanımdan kaldırma uyarısı üretilir; bu uyarılar -W:deprecated ile veya Warning[:deprecated] = true ayarlanarak görünür olur; değişikliği tamamen kapatmak için --disable-frozen-string-literal komut satırı seçeneği vardır.

Ruby 4.0 NEWS dosyası ise dil değişiklikleri bölümünde bu konuda yeni bir madde içermez; metin literallerinin varsayılan olarak donmuş hale geldiğine dair bir kayıt yoktur. Konuyla ilgili tek değinme, uygulama iyileştirmeleri altında donmuş metinler ve sembol tablosunun içeride kilitsiz bir hash kümesi kullanmaya başlamasıdır. Sihirli yorum belgesi de varsayılanın false olduğunu ve bunun --enable=frozen-string-literal ile değiştirilebileceğini söyler. Ruby 4.0.9 ile yapılan deneme bunu doğruluyor:

bash
$ cat uyari.rb
s = "merhaba"
s << "!"
puts s

$ ruby uyari.rb
merhaba!

$ ruby -W:deprecated uyari.rb
uyari.rb:2: warning: literal string will be frozen in the future (run with --debug-frozen-string-literal for more information)
merhaba!

$ ruby --enable=frozen-string-literal uyari.rb
uyari.rb:2:in '<main>': can't modify frozen String: "merhaba" (FrozenError)

Uyarının metni literallerin ileride donacağını söylüyor; bu yüzden yeni yazdığınız dosyalara sihirli yorumu eklemek ya da değiştireceğiniz metinleri baştan +"..." veya String.new ile oluşturmak, gelecekteki sürümlere geçişi kolaylaştırır. Yorumu olmayan bir dosyadaki literal için frozen? şu an false döndürür. Ruby 4.0'daki diğer değişiklikler için Ruby 4.0 yenilikler yazısına bakın.

Hash anahtarları ve semboller

Belgelere göre donmamış bir metni hash anahtarı olarak kullandığınızda hash onun yerine donmuş bir kopyasını saklar. Böylece anahtarı dışarıdan değiştirip hash'i bozmanız engellenir. Semboller ise zaten her zaman donmuştur; bu yüzden sabit anahtarlar için sık tercih edilirler. Farkların ayrıntısı Ruby symbol ve string farkı yazısında.

ruby
anahtar = "ad"
h = {anahtar => "Ali"}
p anahtar.frozen?
p h.keys.first.frozen?
p h.keys.first.equal?(anahtar)
# çıktı: false
# çıktı: true
# çıktı: false

Kendi nesnelerinizi dondurmak

freeze her nesnede çalışır. Dondurulmuş bir nesnenin örnek değişkenlerini değiştiren her metot, attr_accessor ile üretilen yazıcılar dahil, FrozenError verir. Değer nesneleri için baştan değişmez tasarlanmış Data sınıfı daha temiz bir seçenektir; Ruby Struct ve Data sınıfı yazısında anlatılıyor.

ruby
class Sepet
  attr_accessor :toplam
end

sepet = Sepet.new
sepet.toplam = 100
sepet.freeze

begin
  sepet.toplam = 200
rescue FrozenError => e
  puts e.class
end
p sepet.toplam
# çıktı: FrozenError
# çıktı: 100

Özet tablo

İşlemSonuçNot
obj.freezeobj (donmuş)Geri alınamaz, yüzeyseldir
obj.frozen?true veya falseDurumu sorgular
obj.dupDonmamış kopyaextend edilen modüller kopyalanmaz
obj.cloneDurumu korunan kopyaclone(freeze: false) ile çözülmüş kopya
+strDeğiştirilebilir metinGerekirse kopya döndürür
-strDonmuş metinAynı içerikleri tekilleştirebilir
# frozen_string_literal: trueLiteraller donmuşAra değerli literaller hariç

Sık yapılan hatalar

Sık yapılan hatalar: freeze ile derin dondurma beklemek, Sabit atamasını FrozenError sanmak, Sihirli yorumu yanlış yere koymak, clone ile değiştirilebilir kopya beklemek
  • freeze ile derin dondurma beklemek: İç diziler ve hash'ler değiştirilebilir kalır.
  • Sabit atamasını FrozenError sanmak: Bir sabite yeniden değer atamak nesneyi değiştirmek değildir; dondurma yalnızca nesnenin içeriğini korur.
  • Sihirli yorumu yanlış yere koymak: Belgelere göre yorum dosyanın ilk yorum bölümünde olmalıdır; koddan sonra yazılırsa etkisizdir.
  • clone ile değiştirilebilir kopya beklemek: Donmuş nesnenin clone sonucu da donmuştur; dup veya clone(freeze: false) kullanın.
  • Metni yerinde değiştirmek yerine yenisini üretmek gerektiğini unutmak: Donmuş metinlerde << yerine + veya ara değer ile yeni metin oluşturun.
  • Ruby 4.0 ile literallerin donduğunu varsaymak: NEWS dosyası böyle bir değişiklik listelemiyor; davranışı varsayım yerine -W:deprecated ile kontrol edin.

Sık Sorulan Sorular

Donmuş bir nesneyi çözebilir miyim?

Hayır. Belgelere göre dondurulmuş bir nesneyi çözmenin bir yolu yoktur. Yapabileceğiniz şey dup veya metinler için +str ile değiştirilebilir bir kopya almaktır.

Ruby 4.0'da metin literalleri varsayılan olarak donmuş mu?

Hayır. Ruby 4.0 NEWS dosyası böyle bir değişiklik içermez ve Ruby 4.0.9 ile yorum olmayan bir dosyada "metin".frozen? false döner. Ruby 3.4'ten beri bu literalleri değiştirmek, uyarılar açıksa "literal string will be frozen in the future" uyarısı üretir.

frozen_string_literal yorumunu her dosyaya eklemeli miyim?

Yeni yazdığınız dosyalarda eklemek, değiştirilecek metinleri bilinçli olarak +"..." veya String.new ile oluşturmanızı sağlar ve uyarıda belirtilen gelecekteki davranışa hazırlar. Mevcut bir projede ise önce -W:deprecated ile çalıştırıp uyarı veren satırları düzeltmek daha güvenli bir yoldur.

FrozenError mesajında hangi nesne olduğunu nasıl görürüm?

Mesaj genellikle nesnenin sınıfını ve içeriğini içerir; nesnenin kendisine ise e.receiver ile ulaşırsınız. Hata yakalama genel olarak Ruby begin, rescue, ensure yazısında anlatılıyor.

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 string metotları · Ruby symbol ve string farkı · Ruby Struct ve Data · Ruby Ractor · Ruby 4.0 yenilikler · Ruby hata yönetimi