10 puan yazan GN⁺ 2025-08-28 | 3 yorum | WhatsApp'ta paylaş
  • Rust, güçlü güvenlik garantileri sayesinde büyük kod tabanlarında bile refactoring işlemlerini güvenle yapmayı mümkün kılar; bu da üretkenliği ve bakım yapılabilirliği artırır
  • Asenkron zamanlama ile ilgili hataları derleyici önceden tespit eder, tanımsız davranışı önleyerek kararlılığı güçlendirir
  • TypeScript gibi dillerde gevşek tip sistemi nedeniyle asenkron hatalar çoğu zaman production ortamında fark edilir
  • Rust'ın tip sistemi, kod değişikliklerinin etkisini açıkça göstererek karmaşık projelerde güveni ve deneme isteğini artırır
  • Zig, Rust'ın aksine hata işlemede daha gevşek denetimler nedeniyle yazım hatalarından kaynaklanan bug'ları kaçırabilir; bu da güvenilirliği düşürür

Özet ve arka plan

  • Lubeno'nun backend'i %100 Rust ile yazıldı ve kod tabanı büyüdükçe tamamını zihinde tutmanın zorlaştığı bir aşamaya ulaştı
    • Büyük projelerde genelde değişikliklerin yan etkilerini doğrulamak zorlaştığından üretkenlik düşüşü yaşanır
  • Rust'ın güvenlik garantileri, kod değiştirildiğinde etkinin nereye uzandığını açıkça göstererek refactoring konusundaki korkuyu azaltır
    • Bu da uzun vadeli bakım yapılabilirliğe ve üretkenlik artışına katkı sağlar
  • Bu yazı, Rust derleyicisinin bir asenkron bug'ı tespit ettiği bir örnekle başlayıp Rust'ın üretkenlik avantajlarını inceliyor

Rust'ın güvenlik garantilerine bir örnek

  • Sorun durumu: Bir struct, eşzamanlı erişim için mutex ile sarılıyor; kilit alındıktan sonra asenkron işlem yapılıyor
    let lock = mutex.lock();  
    db.insert_commit(commit).await;  
    
  • Sorunun fark edilmesi: rust-analyzer hata göstermese de router tanım dosyasında derleme hatası oluştu
    .route("/api/git/post-receive", post(git::post_receive))  
                                         ^^^^^^^^^^^^^^^^^  
    error: future cannot be sent between threads safely  
    
  • Neden analizi:
    • Web framework'ü, her HTTP bağlantısı için asenkron task oluşturur ve görev zamanlayıcısı bu task'ları thread'ler arasında taşıyabilir
    • Mutex, kilidin aynı thread'de bırakılmasını gerektirir; .await noktasında thread değişirse tanımsız davranış oluşabilir
    • Rust derleyicisi kilidin ömrünü izler ve başka bir thread'de bırakılma ihtimalini tespit eder
  • Çözüm yöntemi: .await öncesinde kilidi bırakmak
  • Önemi: Rust, geliştirme ortamında yeniden üretmesi zor asenkron bug'ları derleme zamanında engeller

TypeScript ile karşılaştırmalı örnek

  • Sorun durumu: TypeScript kodunda asenkron yönlendirme bug'ı oluşuyor
    if (redirect) {  
        window.location.href = redirect;  
    }  
    let content = await response.json();  
    if (content.onboardingDone) {  
        window.location.href = "/dashboard";  
    } else {  
        window.location.href = "/onboarding";  
    }  
    
  • Sorunun nedeni:
    • window.location.href anında yönlendirme yapmaz; yönlendirmeyi zamanlar ve kod çalışmaya devam eder
    • race condition nedeniyle istenmeyen yönlendirme oluşur
  • Çözüm yöntemi: if bloğuna return eklemek
    if (redirect) {  
        window.location.href = redirect;  
        return;  
    }  
    
  • Sınır: TypeScript'te ömür takibi veya borrowing kuralları olmadığından bu tür hatalar derleme zamanında tespit edilemez
    • Production ortamında fark edilir ve debug için uzun zaman harcanır

Rust'ın refactoring avantajları

  • Web geliştirmede Python, Ruby, JavaScript/Node.js başlangıçta yüksek üretkenlik sunar; ancak kod tabanı büyüdükçe gevşek bağlılık nedeniyle değişiklik yapmak zorlaşır
    • Değişiklikten sonra beklenmedik hatalar ortaya çıkar ve kodu değiştirme isteği azalır
  • Rust'ta tip sistemi, değişikliklerin etkisini açıkça gösterdiği için refactoring korkusunu azaltır
    • Örneğin, “bu değişiklik başka bölümleri etkileyebilir” uyarısı sorunları önceden engeller
  • Kod tabanı büyüse bile üretkenlik artabilir; mevcut kod yeniden kullanılabilir ve değişiklik yapılırken kararlılık korunur

Testlerle karşılaştırma

  • Testler, refactoring sırasında regresyonu önlemede faydalıdır; ancak derleyici tarafından zorunlu kılınmadıkları için atlanabilirler
    • Test yazarken soyutlama düzeyi, davranış mı yoksa uygulama ayrıntıları mı test edileceği ve hataları gerçekten önleyip önlemediği gibi kararlar vermek gerektiğinden zihinsel yük yüksektir
  • Rust'ta derleyici, yaygın hataları önceden engeller ve testlerle ilgili karar yükünü azaltır
    • Tip sistemiyle doğrulanamayan özellikler ise testlerle tamamlanır

Zig ile karşılaştırma

  • Zig, Rust'a benzer bir sistem programlama dili olsa da hata işlemede daha gevşektir
    • Örnek hata işleme kodu:
      const FileError = error{ AccessDenied };  
      fn doSomethingThatFails() FileError!void {  
          return FileError.AccessDenied;  
      }  
      pub fn main() !void {  
          doSomethingThatFails() catch |err| {  
              if (err == error.AccessDenid) {  
                  std.debug.print("Access was denied!\n", .{});  
              }  
          };  
      }  
      
    • AccessDenid yazım hatası bug'a yol açar; ancak Zig derleyicisi bunu sayı olarak değerlendirip derlemeyi başarılı sayar
  • switch ifadesi kullanıldığında yazım hatası tespit edilirken, if ifadesinde göz ardı edilir; bu da güvenilirlik sorununa yol açar
  • Rust, bu tür tasarımsal boşlukları önler; yazım hataları ve mantıksal hatalar sıkı biçimde denetlenir

Çıkarımlar

  • Rust, güvenlik garantileri ve katı tip sistemiyle büyük projelerde üretkenliği ve kararlılığı artırır
  • Asenkron bug'lar gibi karmaşık sorunları bile derleme zamanında tespit ederek bakım maliyetini düşürür
  • TypeScript ve Zig örnekleri, gevşek denetimlerin doğurduğu riskleri gösterirken Rust'ın katı derleyicisinin değerini vurgular
  • Rust, web geliştirmede de yalnızca başlangıç üretkenliği açısından değil, uzun vadeli kod tabanı yönetimi için de güçlü bir araç haline gelir

3 yorum

 
taptaps 2025-08-30

Bunu görünce her seferinde, "bu en iyisi, bu çok güçlü bir dil!!" denildiğinde aklıma şu geliyor:
Düşündüğümden çok daha az Rust geliştiricisi var, o yüzden herkesi Rust kullanmaya mı ikna etmeye çalışıyorlar acaba??

 
colus001 2025-08-29

Rust ile ilgili öneri yazıları bana sanki gurme programlarındaki “Deneyin! Deneyin!” gibi geliyor; herhalde bunu sadece ben düşünmüyorumdur, değil mi?

 
GN⁺ 2025-08-28
Hacker News görüşleri
  • Geçen yıl Rust ile yazılmış virtio-host ağ sürücüsünü port ettim. Backend’i, kesme mekanizması geçişini ve kütüphaneden bağımsız çalışan bir sürece dönüştürmeyi yaptım. Bellek eşleme, VM kesmeleri, ağ soketleri ve çoklu iş parçacığını kapsayan karmaşık bir programdı. Rust deneyimim neredeyse yoktu, virtio deneyimim de azdı ama proje derlenir hale geldiğinde neredeyse kusursuz çalıştı. Drop ile ilgili tek bir hata dışında onu da kolayca düzelttim. Rust kütüphanelerinin yanlış kullanılmayacak şekilde tasarlanmış olmasının bana çok yardımcı olduğunu düşünüyorum

    • Uzun süredir Rust ile geliştiriyorum ve çoğu zaman kod derleniyorsa düzgün çalışıyor. Bazen deadlock ya da sıralama ile ilgili hatalar çıkıyor ama genel olarak derlemenin başarılı olması, projenin büyük bölümünün düzgün çalıştığı anlamına geliyor
  • Bence Rust harika. Ama href atama hatasının TypeScript’in suçu olduğu görüşüne katılmıyorum. Sorunun özü, href ayarlansa bile sayfa geçişinin hemen olmaması ve daha sonra işlenmesi. Aynı sorun Rust’ta da olabilir. Eğer Rust’ta bir set_href fonksiyonu olsaydı ve bu davranış daha sonra işlenseydi, aşağıdaki gibi bir kod mümkün olurdu:

    set_href('/foo')

    if (some_condition) { set_href('/bar') }

    Rust’ta bunun böyle tasarlanacağını sanmıyorum. Setter içinde davranış gerçekleşmesi iyi bir kütüphane tasarımı değil ve href atandığı anda sayfa geçişinin olmaması da tuhaf. Rust standart kütüphanesinde böyle aptalca bir uygulama olmazdı. Bu Rust vs TypeScript meselesi değil, Rust standart kütüphanesi ile Web Platform API’si arasındaki fark. Rust’ın böyle bir kullanıcı deneyimi sunmayacağına katılıyorum

    • Resmî olarak konuşursak, setter içinde anında davranış olacak şekilde tasarlamak arzu edilir değil. İsimlendirmeyi de navigate_to(href) gibi yapmak daha doğru olur. Tarayıcı ortamında JS kodunun tamamı callback olarak çalıştığı ve event loop tarafından kontrol edildiği için, anında çalışmaması da doğal bir durum

    • Rust örneği ilginç ama yalnızca TypeScript örneğinden TS’nin büyük ölçekli projelere uygun olup olmadığını anlayamayız. Ruby’de sık sık çalışma zamanında hata yakalamam gerektiği için tedirgin oluyorum ama sonuçta commit atmadan önce iyi çalışıyor ve kodu okuyup değiştirmek kolay olduğu için memnunum. Konum değiştirme meselesi JavaScript’in problemi ve TS’nin miras aldığı bir şey. JS özelliklerin keyfi biçimde değiştirilmesine izin verdiği için böyle olmuş. Ama sayfa anında kaybolmadığı için, bu davranış öğrenildikten sonra mantıklı geliyor

    • Teknik olarak Rust’ta set_href fonksiyonunun () veya ! döndürmesiyle anlamı daha net ima edilebilir. Ama koşullu yönlendirmelerde yanlış kullanımı yakalamak yine de zor olurdu

    • Benim amacım, Rust’ın sahiplik modeliyle window.set_href('/foo') çağrısında window sahipliğinin alınacağı ve bu yüzden API’nin iki kez çağrılmasını imkânsız kılacak şekilde tasarlanabileceğini söylemekti. TypeScript’te ömür takibi diye bir kavram olmadığı için bu mümkün değil. JS API’si zaten mevcut olduğundan TypeScript tarafında sahiplik sistemi eklemenin de bir yolu yok. Rust’ın çeşitli özelliklerinin birleşerek daha güçlü güvenceler sağlayabildiğini göstermek istemiştim

    • Rust’ın daha iyi olduğunu savunurken dayanağın en sonunda “Rust programcıları daha iyi olduğu için” noktasına çıktığı izlenimi veriyorsun. Rust programcılarının böyle döngüsel bir argüman kuracağını sanmıyorum

  • Atamadan sonraki kod, açıkça erken return edilmediği sürece çalışmaya devam eder. Cidden, bir değer atamanın script çalışmasını durduracağını neden düşündüğünü anlamıyorum. TS örneğinde bağlam eksik olabilir ama bunu “data race” diye sunmak tuhaf bir örnek

    • window.location.href değerine atama yapıldığında tarayıcıda o bağlantıya gitme yan etkisi oluşur. Bu davranış beklenmedik ve basit bir atamanın yeni sayfa yüklemesi execve gibi bir his verdiğinden, JS çalışmasının anında duracağını düşünmek de çok garip değil. Programlama yaparken buna güvenmemek gerekir ama davranış gerçekten tuhaf olduğu için kafa karıştırıcı olabilir

    • İnsan bunu düşünmüş olsun ya da olmasın, böyle bir hata biri söylediğinde düzeltmesi nettir. Yazarın asıl anlatmak istediği, TS’nin yakalayamadığı bu tür hataların gerçekte bulunmasının zor olabildiği ve çok zaman alabileceğidir

    • exit(), execve() vb. gerçekten çalışmayı anında durdurduğu için, yönlendirme davranışının da böyle olduğunu düşünmek mümkün

    • Sırf kendi deneyimini paylaştı diye bunu sorun etmek garip

    • Bu atama, sayfadan ayrılmaya yol açan büyük bir yan etki taşıyor. Bunu anında çalışan asenkron bir aksiyon gibi düşünmek de çok mantıksız değil. Ben de böyle varsaymıştım

  • Bu, geliştiricinin statik tip sistemlerinin faydalı olduğunu fark ettiğini anlatan bir hikâye. Böyle yazıları gördüğümde hep eğleniyorum

    • Blogumda göstermek istediğim şey, Rust’ın yaşam süresi takibi ile trait sisteminin sadece basit tip uyuşmazlıklarını değil, çok daha karmaşık sorunları da yakalayabildiğiydi. TypeScript de statik tipli bir dil ama Rust kadar güçlü garantiler veremiyor
  • Avantajların çoğu aslında statik tipli, yani derlenen bir dil kullanmaktan gelmiyor mu? Java, Go, C++ için de benzer şeyler geçerli. TypeScript’in bir numarası var; JS’ye derleniyor ve JS’nin sorunlarını da miras alıyor ama yine de kullanılabilir. Rust’ın tip sistemi daha katı olduğu için fazladan derleme zamanı kontrolleri alabiliyorsunuz ama buna karşılık öğrenmesi ve okuması daha zor diye düşünüyorum

    • Bir ölçüde katılıyorum ama Rust’ın tip sisteminde sahiplik, paylaşımlı/özel erişim, thread güvenliği, sum type gibi daha fazla boyut var. Sahiplik/ödünç alma sistemi sayesinde argüman aktarımının geçici bir görünüm mü yoksa tamamen devretme mi olduğu netleşiyor. Bu, büyük programlarda veya harici kütüphaneler kullanırken çok faydalı. Örneğin Go’nun slice tipi, hangi işlemlerin çalışma zamanında izinli olduğunu net göstermiyor ve onu salt okunur şekilde ödünç vermenin yolu da belirsiz. Rust tip sistemi düzeyinde thread güvenliği garantisi verebildiği için, başka dillerde çalışma zamanında bulunması zor olan data race’leri derleme zamanında engelleyebiliyor

    • Bütün statik tipli dilleri tek bir şeymiş gibi görmek, union(sum) type ve pattern matching’in gerçek gücünü henüz hissetmemekten kaynaklanıyor. Bir kez union type’a alışınca diğer geleneksel statik tipli diller tatmin etmemeye başlıyor

    • Büyük avantajlardan biri de traits / impl traits. Rust, C#’taki Extension Method’a benzer şekilde herhangi bir tipe sonradan trait eklenmesine izin veriyor. Çoğu dilde bir tip kütüphanede tanımlandığında neyse odur, ama Rust’ta basit tiplere işlevselliği aşama aşama eklemeye devam edebilirsiniz. Bu late-bound karakter, tip sistemine bir tür dinamizm katıyor. Biraz abartılı söylemek gerekirse Rust’ın gerçek süper gücü borrow checker’dan çok tip sisteminin açıklığı ve esnekliği. Her şeyi en baştan tasarlamak zorunda değilsiniz; kademeli olarak genişletebilirsiniz

    • Her statik tipli dil aynı etkiyi üretmez. Java sonuçta Object ve çalışma zamanı cast’lerine dayanır. Go’da enum yok. C++’a variant eklendi ama güvenli kullanmak için try/except benzeri manuel işlemler gerekiyor, bu da yapısal olarak rahatsız edici

    • Rust’ı öğrenmenin zor olduğundan bahsediliyor ama aslında gerçekten öğrenirseniz zor değil. Bir şeyleri biraz gelişi güzel yazıp çalışır hale getirmek, kodlamanın erken aşamalarında önemli olabiliyor ve Rust bu yaklaşıma pek dost bir dil değil. Başlangıç dili olarak önermem ama okunması zor değil

  • Rust’ın güçlü güvenlik özellikleri sayesinde kod tabanına dokunurken özgüven artıyor. Bu özgüvenle çekirdek bölümleri refactor etmek de korkutucu olmuyor ve sonuçta üretkenlik ile bakım yapılabilirlik büyük ölçüde artıyor. Ama zaten bu etki için test yazıyoruz. Test yoksa katı bir derleyici çok yardımcı olur ama testleri iyi yazarsanız hangi dil olursa olsun güvenle refactor edebilirsiniz

    • Mümkün olan kısımları derleyicinin statik olarak kanıtlaması daha iyidir. Testleri, yalnızca statik güvencenin zor olduğu durumlarda kullanmak en uygunu. İdeal son nokta formel doğrulama olurdu ama pratikte çok zor; genel bir çözüm değil ama ilke olarak doğru

    • İyi testler ve iyi kullanılan tip sistemi, ikisi de hata yakalamada etkili. Ama test yazmak bazen xkcd’deki “Standards” karikatürünü hatırlatıyor. Standardı düzeltmek için bir standart daha üretmek gibi, burada da hataları yakalamak için daha fazla kod yazıyoruz. Yine de tip sistemi bakımını dil tasarımcıları yapıyor; her projede ayrıca yönetmek gerekmiyor

    • Kodu her refactor ettiğinizde testleri de refactor etmek gerektiği için iş iki katına çıkıyor

  • Bence Rust ya da F#’ın tip sistemi, kodu refactor ederken en çok parlıyor. “Korkusuz refactor” ifadesi tam oturuyor

    • Dezavantajı şu ki Rust tamamlanmamış koda tahammül etmiyor; bu yüzden refactor sırasında “kısmen çalışan kod” bulundurmak mümkün olmuyor. Ya tamamen bitireceksiniz ya da hiç başlamayacaksınız; bu da deneysel kod yazarken rahatsız edici olabiliyor. Ama bu katılığın sonunda daha iyi koda yol açtığını da düşünüyorum
  • Zig örneği sarsıcı. O kadar güvensiz görünüyor ki böyle bir tasarımın nasıl iyi bulunabildiğini anlamıyorum

    • Bunun muhtemelen bir bug olduğunu düşünüyorum. Ama Zig gibi yaratıcısı merkezli bir dilde, bug’ın düzeltilmesi için o yaratıcının da bunun bug olduğunu kabul etmesi önemli. Eğer niyetli bir tasarım olarak görülürse aynı şekilde devam edebilir

    • Her dilde biraz güvensiz tasarım vardır. Örneğin Go veya Zig’de mutex.unlock() her zaman açıkça çağrılmalı; scope dışına çıkınca otomatik serbest bırakma yok. Öte yandan Rust’taki as operatörü gibi sayısal tipler arası dönüşümler fazla kolay ve ben bunun yüzünden bütün gün bug aradım

    • Başta o hatayı fark etmemiştim, bu yorum sayesinde gördüm

    • Linter, sistem içinde var olmayan hata referanslarını yakalayıp switch kullanımını öneren uyarılar verebilir diye düşünüyorum

    • Hata kümesinin fonksiyon imzasına göre üretildiğini sanıyordum. Biraz tuhafmış

  • Güçlü ve sağlam bir statik tip sisteminin çeşitli özellikler sunmasını seviyorum. Ben de Haskell kod tabanında (1 milyon SLOC) büyük ölçekli refactor’ların kolay olduğu deneyimini yaşadım. Gelişmiş özellikler olmadan bile, yalnızca tip sistemiyle bu mümkün olabiliyordu

  • Rust, await sınırında kilidi tutuyor olmayı doğru biçimde algılamış ama o kilidi await öncesinde bırakmanın gerçekten güvenli olup olmadığı ek bağlam gerektiriyor. Bana göre kilit, transaction commit oluşturulana kadar tutulmalı; await öncesi bırakılırsa eşzamanlılık sorunları doğabilir. Rust async konusunda uzman değilim ama commit sonrasında join ya da select ile engellemek gerekmiyor mu diye düşünüyorum

    • Eğer await sırasında kilidi elde tutmanız gerekiyorsa, async-aware bir mutex kullanabilirsiniz. futures veya tokio crate’leri bu tür kilitler sunuyor. Genelde uzun süre tutulacaksa ya da await noktaları arasında kilit korunacaksa kullanılır. Normal kilitlerden daha maliyetlidir

    • await sınırlarında da kilidi korumanız gerekiyorsa Tokio’nun async-aware mutex’ini kullanabilirsiniz. tokio/sync/struct.Mutex belgelerine bakın