Rust'un özü
(jyn.dev)- Rust, çeşitli kavramların birbiriyle sıkı biçimde iç içe geçtiği bir dil; bu yüzden temel bir programı anlamak için bile birçok unsuru aynı anda öğrenmek gerekir
- Fonksiyonlar, generics, enum'lar, pattern matching, trait'ler, referanslar, ownership,
Send/Sync,Iteratorgibi unsurların tümü birbiriyle etkileşime girecek şekilde tasarlanmış temel öğelerdir - JavaScript ile karşılaştırıldığında, JS'te yalnızca bazı kavramları bilerek kod yazmak mümkünken, Rust'ta ancak dilin bütün bağlamı anlaşıldığında anlamlı kod yazılabilir
- Rust'taki bu karmaşıklık öğrenme eşiğini yükseltir, ama aynı zamanda güvenlik ve tutarlılık sağlar ve kod tasarlama biçimini güçlü şekilde etkiler
- Bu dilsel örgü, Rust'u özel kılar; “Daha küçük Rust” vizyonu da incelikle birbirine bağlanmış bir dil felsefesini yeniden düşünmeye sevk eder
Rust öğrenmenin zorluğu
- Rust'un giriş eşiği yüksek olsa da birçok kişi dokümantasyon, API ve tanılama iyileştirmelerine katkıda bulunmuştur
- Temel kavramlar arasında birinci sınıf fonksiyonlar, enum'lar, pattern matching, generics, trait'ler, referanslar, borrow checker, eşzamanlılık güvenliği, iterator'ler yer alır
- Bu kavramlar birbirine bağımlı ve iç içe geçmiş olduğundan tek tek ayrı öğrenilmeleri zordur; standart kütüphane de büyük ölçüde bu özellikleri kullanır
- Yaklaşık 20 satırlık bir Rust kodunu anlamak için bile fonksiyonel paradigma,
Resultve hata işleme, generic tipler, enum'lar, iterator'ler gibi birçok öğeyi aynı anda kavramak gerekir
Rust ve JavaScript karşılaştırması
- Aynı dosya değişikliği algılama programı Rust ve JS ile yazıldığında, Rust tarafında çok sayıda dil kavramı birbirine dolanmıştır
- JS'te temelde yalnızca fonksiyonlar ve null işleme anlaşılırsa çalışan kod yazmak yeterlidir
- Bu, Rust'un sadece daha zor olduğu anlamına gelmez; Rust'un dilin tamamının yapısal olarak anlaşılmasını gerektiren bir tasarım olduğunu gösterir
Rust'un karşılıklı bağlı tasarımı
- Rust'un özü, organik biçimde tasarlanmış özelliklerin birleşimidir
- Enum'lar pattern matching olmadan kullanışsız kalır; pattern matching de enum'lar olmadan kısıtlıdır
ResultveIterator, generics olmadan uygulanamazSend/Synckavramı ileprintlnkısıtları, trait'ler olmadan güvenli biçimde ifade edilemez- Borrow checker, closure'ların capture analizini kullanarak
Send/Syncgüvenliğini garanti eder
- Bu karşılıklı bağ, Rust'u yalnızca bir özellikler toplamı değil, bütünleşik bir dil sistemi haline getirir
Daha küçük Rust vizyonu
- 2019'da without.boats, “Smaller Rust”tan söz ederek küçük ve rafine bir Rust olasılığını tartıştı
- Bugün Rust çok daha büyümüş olsa da, daha küçük Rust fikri incelikle kenetlenmiş dil tasarımının özünü hatırlatır
- Rust'un çekiciliği, dil öğelerinin birbirinden bağımsızken bir araya geldiklerinde güçlü ifade kabiliyeti ve güvenlik sunmasında yatar
Sonuç
- Rust'u öğrenmek zordur, ancak birbirine geçmiş kavramların tutarlılığı ve bütünlüğü büyük bir güçlü yön olarak öne çıkar
- Bu yapı sayesinde Rust, geliştiricilerin yalnızca kod yazmasını değil, güvenlik ve performansı aynı anda düşünen bir yaklaşım benimsemesini sağlar
- Rust'un özü, “küçük ve incelikle tasarlanmış bir çekirdek dil”dedir; bu da günümüzde genişlemiş Rust'ta bile önemli bir felsefe olarak varlığını sürdürür
1 yorum
Hacker News yorumu
fs.watchdokümanında, callback içindefilenamedeğerininnullolabileceğinin mutlaka kontrol edilmesi gerektiği açıkça yazıyor. Rust'ta bu gerçek tip sistemine yansıtılır ve seni bunu zorunlu olarak ele almaya iterdi, ama JS'te kodu gelişigüzel yazmak çok kolay. İlgili dokümannullkontrolü zorunlu olur. Bu yüzden bence TS'nin, JS dünyasında Rust'taki doğruluk anlayışına daha yakın, nispeten düşük maliyetli bir ara adım olduğunu gösteren iyi bir örnekfor path in paths,for (const path of paths)olmalı. JS parantez yoksa zaten hemen hata verir amainileofarasındaki fark şu: biri değerleri değil indeksleri (iterable index) dolaşır, dolayısıyla indeks gerçekten string'e çevrilipfs.watch'ın ilk argümanı olarak geçer. Hatta TypeScript bile bu hatayı yakalayamayabilirkind'in nereden geldiğini merak ediyorum.console.log("${kind} ${filename}")içindekinddeğil,eventType(string) olması gerekirdiprintln, yalnızcaDisplayveyaDebugtrait'ini uygulayan tipleri yazdırabilir. Bu yüzdenPathdoğrudan yazdırılamaz. Her işletim sistemi yolları UTF-8 olarak saklamaz ve Rust'ın string tipleri bütünüyle UTF-8'dir. YaniPathyazdırmak kayıplı olabilir.Path,displaymetodu üzerindenDisplayuygulayan bir tip döndürür. Rust bunu tip sistemine işlemiş durumda ama JS/TS'te içteki string'in UTF-16 olduğunu açıkça ifade etmek zordur; Unicode olmayan yolları doğru işlemek için doğrudanTextEncoder/Decoderkullanmak gerekir. Eski deneyimlerime göre, sunucu Shift_JIS ile metin gönderdiğinde bunuresponse.text()ile okuyunca çalışma zamanında yalnızca boş string dönüyordu. Kodlama meselelerine alışkın değilsen böyle bir durumda günlerce debug yapabilirsin. Ayrıca JS örneğinde, Rust kodunda olmayan hatalar ve sözdizimi yanlışları var (for-inyerinefor-ofgerekli). Bu örnek için sadece "birinci sınıf fonksiyonlar" kullanılıyor demek de pek doğru değil; Rust'taki gibi iterator mantığını da anlaman gerekiyor ve CommonJS kullanılıyor. Üstelikasync/await,Promisesve top-level await gibi şeyleri de ayrıca öğrenmek gerekiyor; top-level await ise Node dahil bazı çalışma zamanlarında ancak yakın zamanda desteklendi. Hâlâ bazı JS motorlarında da yok (ör. React Native'in Hermes'i)Rust'ı kullanmaya devam etmemin nedeni tam da bu. Bu örnek sadece bir tanesi ama böyle küçük sorunlar ve tuzaklar diğer dillerde hep etrafa saçılmış durumda. Tek tek bakınca belki hiç yaşanmayabilir ama bir programın tüm yaşam döngüsü boyunca birikince, bir yerlerden sürekli garip hatalar fırlar ve onları durmadan aramak zorunda kalırsın. Rust'ta bu olmuyor. Tip sistemi akıl almaz ölçüde çok durumu daha baştan engelliyor. Gerçekten de Rust'la özellikleri tamamlanmış bir yazılımı yayına aldıktan sonra, sadece ara sıra yeni özellik ekledim; genel hata ayıklama yükü neredeyse tamamen ortadan kalktı. Elbette her yerde mantık hataları olabilir ama diğer dillerdeki aptalca tip/yapı uyuşmazlıklarından doğan sorunları en baştan kapattığı için üretkenlik ve bakım deneyimi bambaşka oluyor
Kişisel olarak, JS/TS'te
thenable/Promiseveasync-awaitkonusunu gerçekten iyi bilen geliştirici sayısının çok olmadığını düşünüyorum. Şuna benzer şeyler de gördüm:Callback biçimindeki bir sarmalayıcıyı aynen
Promiseile paketleyip sonra bunuasyncfonksiyon içinde tekrar kullanıyorlar. Bunu her gördüğümde içim acıyor. Gerçekten bunun gibi kodları her yerde gördüm. Buna bir de modül import'ları,async import(), transpile, code splitting vb. eklenince iş gerçekten karmaşıklaşıyorshebang'i (kendi kendine çalıştırılabilir rust script'i) görür görmez dikkati dağılan başka kimse olmadı mı? Daha önce Go'da benzer bir şeyi fark ettiğimde de aynı tepkiyi vermiştim. Oldukça kullanışlı görünüyor; temel amaçlar için gayet yeterli olabilir. Rust ile build/test pipeline yöneten projelerde de benzerini görmüştüm. Bu kullanım için epey iyi bir alternatif olabilir. Yine de ben çoğu zaman bash'in biraz ötesine geçen bir script gerektiğinde Deno+TS kullanıyorum. En uzun süre JS ile çalıştım (28 yıl), sonra da C# ile 24 yıl. Node'u ilk günlerinden beri kullanıyorum. Deno, paket paylaşımı/merkezileştirme açısından Node ya da Python'dan daha kolay yönetiliyor.cargofrontmatter da benzer şekilde çalışıyorcargo'ya script entegrasyonunu bizzat tasarlayıp uygulayan kişiyim (bu arada uzun süredir üçüncü taraf uygulamalar da vardı). Gerçek kullanım örnekleri görmek beni çok mutlu ediyor; burada anıldığını görmek de sevindirici. Dokümana da bakabilirsiniz. Bunun nasıl görünmesi gerektiği, dille nasıl bağlanacağı, ilk sürüm kapsamının ne olacağı gibi uzun tartışmalar yaşandı. Şu anda stil kılavuzu ve Rust referansı güncellemeleri gibi son işleri yapıyoruz; geriye kalan büyük başlıklarrustfmt,rust-analyzerayrıntıları,rustchata düzeltmeleri ve Cargo'nun hata raporlamasını iyileştirmek. Ben de her gün issue yeniden üretim script'leri yazmak içincargo scriptkullanıyorum-Zscriptözelliğinin anahtar kelimeleriyle arama yapıp araştırmaya dalmıştım, dikkatimin dağılması bundan. 2023'ten beri üzerinde çalışılıyor ve neredeyse tamamlanmış görünen açık issue'lar da var. ZomboDB deposunda da build pipeline'ın Rust ile yönetildiğini gördüm ama tüm bağlamı tam olarak anlayamadım.cargofrontmatter'ın script taşınabilirliği açısından aşırı faydalı olduğunu da söylemek isterim. Tek bir dosyayı paylaşmak yetiyor; Python veya Node.js'teki gibi ek kurulum/başlatma olmadan bağımlılıkları hemen çekip kullanabiliyorsun#!/some/pathile başlayan bir dosya, kabuk tarafından belirtilen komuta tüm dosyastdinolarak verilerek çalıştırılırasyncileconst. O halde doğrudan "asyncveconstgelmeden önceki Rust daha küçük ve daha temizdi" denmesi gerekirdi; yazıda bunun bu kadar açık söylenmemesi bana eksik geldiCopytrait, reborrowing, deref coercion, döngülerde otomatikinto_iter, scope sonunda otomatikdropçağrısı (bu da elle çağrılabilir ya da derleyici hata verebilir), trait bound'larda varsayılan:Sized, lifetime elision,matchergonomics gibi çeşitli otomasyon ve kolaylıkları çıkarırsan gerçekten mekanik olarak daha basit bir Rust elde edebilirsin. Ama böyle bir dili günlük kullanımda yazmak çok rahatsız edici olurdu. İronik olan şu ki bu unsurların çoğu aslında yeni başlayanlar için tasarlanmışasyncveconstgelmeden önce daha küçük ve daha temiz olduğu yönündeydi. Bunu doğrudan söylemememin nedeni, bu özellikleri geliştiren arkadaşlarımın olması. Matklad bunu lobste.rs üzerinde çok iyi ifade ediyor. 2015 Rust'ı daha tamamlanmış ve daha tutarlıydı ama Rust'ın vizyonu mutlak tutarlılık (coherence) değil, endüstride işe yarayan bir dil olmakDeref'i ne zaman ve hangi sırayla uygulayacağı konusunda fazla özgür olduğunu düşünüyorum..into()veFromtrait'i tip dönüşümlerini fazla gizli yapıyor. Standart kütüphanede de bu tür "kolaylık" fonksiyonlarından çok var. Sonuçta nesnenin tipi belirsizleşiyor ve fonksiyon çağrısıyla implementasyon arasındaki bağın takibi zorlaşıyor (tabii IDE biraz yardımcı olursa başka)implicit return) program akışını gizleyip hatalara yol açıyor. Soru işareti operatörünü de pek sevmiyorumvendoretmek gerekiyor; bu gerçekten can sıkıcımove assignmentya daconstanahtar sözcüğünün anlamı gibi, Rust'ta öğrenilen bazı şeyler sonradan geleneksel dillerde edinilmiş kötü alışkanlıkları söküp atma zahmetini de azaltabilirnoneolmadığını kanıtlayamadı" türü mesajlar, bir test durumunda çalışma zamanı çökmesinin yerini doğrudan görmekten çok daha zor gelebilir. Çıktıları satır satır yazdırarak sorun çözmek çoğu zaman daha hızlıdır; ama derleyicinin karmaşık hata mesajlarında takılırsan gerçekten uzun süre kaybolabilirsinmemmodülü desenini takip ediyor, bu yüzden arayüz yapısını gerçekten anlamak istiyorsan std::mem ile başlamak iyi olur