Yeni bir dil öğrenmeye başladığınızda ve depar atacağınızı beklerken ilk adımda tökezlediğiniz o hissi bilir misiniz? Rust ile yaşadığım tam olarak buydu. Birkaç diziyi ayrıştıran ve değiştiren (mutate eden) gayet düzgün bir JavaScript aracım vardı ve bunu bir kahve molasında yeniden yazabileceğimi düşündüm. Editörümü açtım, doğal gelen bir kod yazdım ve derleyiciyi çalıştırdım. Patladı. Sözdizimi (syntax) hatalarıyla değil. Eksik noktalı virgüllerle de değil. Bana, zaten taşınmış (moved) olan bir değeri kullanmaya çalıştığımı söyledi. Ekrana bakakaldım. Taşınmış mı? Hiçbir şeyi taşımadım. Sadece başka bir değişkene atadım.
JavaScript Konfor Alanı
JavaScript sizi veriye ortak bir yazı tahtası gibi davranmanız için eğitir. Bir nesne veya dizi tanımlarsınız, onu bir fonksiyona verirsiniz, o fonksiyonun bir özellik eklemesine izin verirsiniz, sonra onu başka bir yere gönderirsiniz. Çöp toplayıcı (garbage collector), arkada görünmez bir hademe gibi oturur ve bıraktığınız her şeyi süpürmeyi bekler. Asla "Bu string'in sahibi kim?" diye sormazsınız. "Bunu okuyabilir miyim?" diye sorarsınız. Cevap evet ise, ona dokunursunuz. Başka bir referansa ihtiyacınız varsa, sadece let b = a dersiniz ve devam edersiniz. Her iki değişken de aynı bellek bloğuna işaret eder ve eğer a onu değiştirirse, b değişikliği anında görür. Bu kullanışlıdır. Aynı zamanda kaotiktir, ancak çalışma zamanı (runtime) temizliği hallettiği için bu kaos nadiren başınızı yakar.
İlk Derleyici Şoku
Rust size güvenmez. Kulağa sert geliyor ama öğrendiğiniz ilk şey budur. Dil, üç katı kuralı olan bir sahiplik (ownership) sistemi etrafında inşa edilmiştir. Birincisi, her değerin tam olarak bir sahibi vardır. İkincisi, aynı anda yalnızca bir sahip olabilir. Üçüncüsü, o sahip kapsam dışına (out of scope) çıktığında, Rust değeri otomatik olarak düşürür (drop). Çöp toplayıcı yok. Siz açıkça seçmediğiniz sürece arka planda referans sayımı (reference counting) yok. Sadece kodunuz daha hiç çalışmadan önce derleyici tarafından uygulanan bu üç kural var.
O JavaScript aracının Rust versiyonunu yazdığımda, doğal gelen şeyi yaptım. Rust'ın diziye cevabı olan bir vector oluşturdum ve onu ikinci bir değişkene atadım. Sonra birincisini kullanmaya çalıştım. Derleyici reddetti. JavaScript'te let b = a referansı kopyalar. Rust'ta ise sahipliği taşır (moves ownership). Orijinal değişken geçersiz hale gelir. Derleyici bunu, iki yolun yanlışlıkla aynı belleğe basmasını engellemek için yapar; diğer sistemlerde veri yarışları (data races) ve "use-after-free" hataları bu şekilde oluşur.
Rust Neden Oyuncaklarınızı Elinizden Alıyor
JavaScript'teki şu desene bakın:
let a = [1, 2, 3];
let b = a;
a.push(4);
// Both a and b see 4.
Bu ikinci bir doğadır. İki kez düşünmezsiniz. Rust'ta ise aynı sezgi, siz yazmayı bitirmeden derleyici tarafından reddedilmenize neden olur. Bir kez b vektöre sahip olduğunda, a boş bir isimden ibarettir. Ona bir şey ekleyemezsiniz (push). Ona bakamazsınız bile. Bellek artık b'ye aittir.
Neyi engellediğini anlayana kadar bu bir ceza gibi hissettirir. Eğer iki değişken, koordinasyon olmadan aynı heap verisini değiştirebilseydi, bellek bozulması (memory corruption) riskiyle karşı karşıya kalırdınız. Rust, aktarımı açık hale getirerek bu hata kategorisini tamamen ortadan kaldırır. Derleyici titiz davranmıyor; bir kapı bekçisi (gatekeeper) gibi davranıyor. Her adımda, kodunuzun hangi kısmının hangi veriden sorumlu olduğuna karar vermeye zorluyor.
Borrowing: Aslında Bir Özellik Olan Geçici Çözüm
Elbette, her atama sahipliği sonsuza dek devretseydi, program yazmak yorucu olurdu. Her şeyi kopyalamanız (clone) gerekirdi, bu da bellek ve hız kaybına yol açardı. Rust bunu borrowing (ödünç alma) ile çözer.
Borrowing, bir değeri onu almadan kullanmanıza olanak tanır. İki türü vardır ve aradaki fark önemlidir.
- Immutable borrows (değiştirilemez ödünç almalar),
&Tşeklinde yazılır: Bunlar veriyi okumanızı sağlar. İstediğiniz kadar...
