Roc derleyicisinin Rust→Zig yeniden yazım süreci
(rtfeldman.com)- Mevcut uygulamadaki yapısal kusurları gidermek için 300 bin satırlık Rust kodu Zig ile yeniden yazıldı; 487 gün sonunda özellik eşdeğerliğine ulaşıldı ve bu yılın ilerleyen dönemlerinde ilk resmi sürüm numarası olan 0.1.0 hedefleniyor
- Yeni derleyici, hot code loading ve yeniden üretilebilir çapraz derleme, pattern matching içinde string interpolation ve HTTP yönlendirmesinde heap tahsisinin kaldırılmasını destekliyor;
Rocci Bird'ün wasm boyutu da yarıdan fazla küçülerek 31KB'ye indi - Zig'in seçilmesindeki temel nedenler derleme süresi, ayrıntılı allocator ve veri yerleşimi kontrolü, derleyici geliştirmeye uygun ekosistem ve bellek güvensiz kod denetimiydi; Zig 0.17.0'ın incremental build sistemi yaklaşık 460 bin satırı 35ms içinde yeniden derliyor
- Gerçek hata sınıflandırmasında Rust derleyicisinde 21 bellek bozulması, Zig derleyicisinde ise 10 vaka görüldü; ancak bunların çoğu yanlış kod üretiminden kaynaklandı ve Zig derleyicisinin kendi bellek güvenliği hataları dosya adlarını bozan 2 use-after-free vakasıydı
- Zig; pointersız veri yapıları, parse etmeden deserialization ve LLVM bitcode serializer'ını yeniden kullanma açısından çok uygundu; ancak testlerde otomatik bellek serbest bırakma, polymorphism, private struct alanları, dead code tespiti ve sürümler arası uyumlulukta Rust'un geliştirici deneyimi daha iyiydi
Özellik eşdeğerliğine ulaşan yeniden yazım
- Roc derleyici ekibi, yaklaşık bir buçuk yıl boyunca 300 bin satırlık Rust kodunu Zig ile yeniden yazarak mevcut derleyiciyle özellik eşdeğerliğine ulaştı
- Özellik eşdeğerliği önemli bir kilometre taşı olsa da resmi sürüm anlamına gelmiyor; yeni derleyicinin 0.1.0 sürümü bu yılın ilerleyen dönemleri için hedefleniyor
- 2024 tarihli WASM-4 oyunu Rocci Bird artık yeni derleyiciyle derlenebiliyor
- Oyunun tamamı 1.000 satırdan az Roc kodundan oluşuyor
- Güncellenmiş kaynak, orijinalinden daha sade
roc build --opt=sizetarafından üretilen wasm ikili dosyası 31KB boyutunda; bu da mevcut derleyicinin çıktısının yarısından daha küçük
- Ana sayfada, 2.5MB'lık WebAssembly derleyicisiyle temel Roc programları tarayıcıda yazıp çalıştırmak mümkün
- Yeniden yazım 487 gün sürdü; bu, Bun'ın yaklaşık 500 bin satırlık Zig kodunu Rust'a taşıdığı 11 günden 476 gün daha uzun
- Bun doğrudan port edildi, ancak Roc tarafı birden fazla derleyici yapısı ve özelliğinin büyük ölçüde değiştirildiği, yeniden yazıma daha yakın bir çalışmaydı
- Bu nedenle iki kod tabanının büyüklüğü ya da süresini yalnızca Rust ve Zig üzerinden doğrudan karşılaştırmak zor
Yeni derleyicinin sunduğu geliştirici deneyimi
-
Hot code loading ve çapraz derleme
roc server.rocile sunucu çalıştırıldıktan sonra kod değiştirilirse, bir sonraki istekte yeni kod otomatik olarak uygulanıyor- Bu, Python gibi yorumlamalı dillerde yaygın olsa da Roc gibi yüksek performanslı derlenen dillerde nadir görülen bir davranış
- Aynı yaklaşım yalnızca web sunucularında değil, basit 2D oyunlarda da çalışıyor
- Dağıtım sırasında
roc build server.roc, LLVM optimizasyonları uygulanmış bağımsız çalıştırılabilir bir ikili dosya üretiyor roc build --target=x64muslile Alpine Linux için statik ikili dosya çapraz derlenebiliyor- Aynı kaynak baytları girildiğinde, Mac dahil herhangi bir build sisteminde aynı çıktı baytları üretiliyor
-
Pattern matching içinde string interpolation
"/users/${id}"gibi string interpolation, pattern matching içinde destekleniyor- Express tarzı yönlendirmede olduğu gibi çalışma anında template string parse etmek yerine, bunu doğrudan derleyici ele alıyor
- HTTP method ve yolu birlikte eşleştirmek, iç içe yolları ya da varsayılan değerleri pattern ile dallandırmak mümkün
- Örnekteki tam HTTP istek işleme kodu, derleme sırasında tür güvenliğini denetlerken hiç heap tahsisi yapmıyor
- Saf fonksiyonların derleme zamanında çalıştırılmasını kullanarak HTTP istek yönlendirmesi tahsissiz biçimde uygulanmış; sözdizimi ana sayfadaki WebAssembly derleyicisinde denenebiliyor
Neden sıfırdan yeniden yazıldı
- Roc, Rust·C·Zig'in aksine bir sistem dili değil; reference counting tabanlı otomatik bellek yönetimi kullanıyor
- İzlemeli garbage collector duraksamalarından kaçınıyor
- Perceus optimizasyonu ve Koka tarzı fırsatçı mutasyonu kullanıyor
- Tipik sistem dışı diller gibi her closure capture için heap tahsisi yapmak yerine, lambda set specialization ile polymorphic defunctionalization kullanıyor
- Defunctionalization, fonksiyonel dillerde inlining gibi çok sayıda sonraki optimizasyonu mümkün kılıyor; ancak mevcut uygulamayı doğru hale getirmek çok zordu
- Ayaz Hafiz'in OCaml prototipi sayesinde sorunun, birden fazla derleyici aşamasına yayılan yapısal kusurlar olduğu ve çözüm için derleyicinin büyük bölümünün yeniden yazılması gerektiği doğrulandı
- Diğer katkıcılar da farklı nedenlerle çeşitli bölümleri yeniden yazmayı planlıyordu; bu yüzden, neredeyse tamamını adım adım değiştiren Theseus'un Gemisi yaklaşımı yerine tam yeniden yazım değerlendirildi
- Başarılı derleyici projelerinde self-hosting gibi amaçlarla sıfırdan yeniden yazım yaygındır; kendi diline değil Go'ya taşınan TypeScript yeniden yazımı da buna bir örnek
- Roc derleyicisinin self-hosting yapmaması bir ilke olarak sürse de, bu kez yeniden yazımın getirilerinin bilinen maliyetlerden daha büyük olduğuna karar verildi
Neden yeniden Rust yerine Zig seçildi
- Ekip, standart kütüphanenin çeşitli temel işlevlerinde zaten Zig kullanıyordu ve yeterince iyi bildikleri tek sistem dilleri Rust ve Zig olduğu için ciddi değerlendirme bu iki dille sınırlı kaldı
- Her proje için uygun dil farklı olabilir; Rust'u başka işlerde kullanmaya devam etmek ile Roc için Zig seçmek bir çelişki değil
-
Derleme süresi
- Rust'un
cargoderlemeleri incremental build dahil uzun sürüyordu ve kod tabanı büyüdükçe bu başlıca rahatsızlıklardan biri olmaya devam etti - Zig'e geçildiğinde derlemelerin çok daha hızlı olacağı öngörüldü
- Rust'un
-
Bellek ve veri yerleşimi kontrolü
- Her derleme aşamasında farklı allocator'lar, özellikle de arena allocator'lar kullanılıyor ve structure of arrays (SoA) yerleşimi yaygın biçimde tercih ediliyor
- Rust ekosistemi genel olarak tek bir global allocator varsayarken, Zig ekosistemi ayrıntılı allocator geçişini temel alıyor ve SoA desteğini de standart olarak sunuyor
-
Gerekli ekosistemin kapsamı
- Rust'un genel ekosistemi Zig'den büyük olsa da, Roc'un özel gereksinimleriyle ilgili paketler her iki ekosistemde de çok fazla değildi
- LLVM C++ kütüphanelerini sarmak yerine hızlı bitcode üreten kodlar gibi ihtiyaç duyulan niş işlevler Zig tarafında daha fazlaydı
-
Bellek güvensiz kod desteği
- Mevcut 300 bin satırlık Rust derleyicisinde yaklaşık 1.200 adet
unsafekullanımı vardı - Karşılaştırma için Rust derleyicisinin kendisi 3,5 milyon satırda yaklaşık 40 bin
unsafekullanıyor - Makine kodu üreten derleyicilerde bellek güvensiz işlemler işin önemli bir parçası
- Rust, seyrek görülen
unsafebloklarını izole edip Miri veya Valgrind ile denetleme modeline dayanıyor; ancak Roc'taunsafenadir değildi - Zig, bellek güvensiz kodun doğru çalışmasını sağlamak için daha fazla özellik sundu ve ekip en fazla desteği tam olarak bu alanda istiyordu
- Mevcut 300 bin satırlık Rust derleyicisinde yaklaşık 1.200 adet
borrow checker olmadan elde edilen bellek güvenliği
-
Rust ve Zig’de denetim kapsamı
- Microsoft’un 2019 tarihli verilerine göre her yıl güvenlik güncellemeleriyle ele alınan açıkların yaklaşık %70’i bellek güvenliği sorunlarıydı
- Aynı verinin 2018 sınıflandırması Rust ve Zig açısından ayrıldığında tablo şöyle görünüyor
- %83,6’lık kısım sınır dışı okuma-yazma, güvensiz cast, başlatılmamış okuma, stack overflow ve bellek güvenliği dışı sorunlardan oluşuyordu; bu kategori Rust ya da Zig seçimine bağlı değildi
- %16,4’lük kısım use-after-free idi ve Rust’ın borrow checker’ı, Zig
ReleaseSafeve Fil-C tarzı denetimlerle yakalanabilecek türdendi - Zig’in
ReleaseSafemodu, serbest bırakılmış belleğe erişildiğinde çalışma sırasında panic üretir- Rust’ın güvenli alt kümesine göre daha az kapsamlı denetim yapar
- Çalışma maliyeti vardır ve program panic verebilir
ReleaseFastise prodüksiyonda bu denetimleri atlar, ancak debug build’ler ve testlerde korur- Tüm gerçek yürütme yolları test edilirse
ReleaseSafeile benzer güvenlik elde edilebilir, ancak bu kadar geniş test kapsamı pratikte genelde gerçekçi değildir
-
Diğer Zig projeleri ve Rust’taki
unsafe- TigerBeetle,
ReleaseSafekullanıyor; ayrıntılı Jepsen doğrulamasında iki güvenlik hatası bulundu, ancak ikisi de bellek güvenliğiyle ilgili değildi ReleaseFastkullanan Ghostty ve Zig derleyicisinde de Zig kodundaki bellek güvensizliğinden kaynaklanan bir CVE bulunmuyor- Rust programlarında da bağımlılıkların içindeki
unsafenedeniyle bellek güvenliğinde boşluk oluşabiliyor - Unsafe Rust, risk profili açısından
ReleaseFastZig’e benziyor; ancak Zig’in geliştirme sırasında sorunları yakalayan çalışma zamanı denetimlerine karşılık gelen yerleşik bir mekanizma yok - Miri ve Valgrind yardımcı olabilir, ama bunları kullanan Rust projelerinin sayısı fazla değil
- Bunun yerine
unsafekullanımının seyrek tutulması ve daha sıkı gözden geçirilmesi kültürü, pratikte Rust’ın güçlü bellek güvenliği itibarını oluşturuyor - Rust tabanlı projelerde de
unsafekaynaklı açık örnekleri görüldü- Deno’da sınır dışı okuma ve use-after-free CVE’leri vardı
- Rocket’ta bir use-after-free CVE’si vardı
- Actix’te
unsafekullanımının alışılmadık derecede yüksek olduğu dönemde birden fazla bellek güvensizliği CVE’si ortaya çıktı
- Roc, çoğu tahsisin ömrü basit olan arena’larda yapıldığı için use-after-free’yi büyük bir risk olarak görmedi; Zig’in ek denetimlerinin ise doğası gereği güvensiz koda daha faydalı olduğuna karar verdi
- TigerBeetle,
Yeniden yazımdan sonra doğrulanan bellek bozulması hataları
- Claude Opus 4.8 ile Roc issue tracker’ının sınıflandırılması sonucu şöyleydi
| Derleyici | Bellek bozulması var | Bellek bozulması yok | Toplam |
|---|---|---|---|
| Rust | 21 | 2.575 | 2.596 |
| Zig | 10 | 421 | 431 |
- Rust derleyicisindeki 21 bellek bozulması vakası, derleyici iç mantığındaki bir bozulmadan kaynaklanmıyordu
- borrow checker amaçlandığı gibi çalıştı
- Bunlar, derlenen programda belleği bozan yanlış üretilmiş makine kodundan kaynaklanan yanlış derleme hatalarıydı
- Zig derleyicisindeki 10 bellek bozulması vakasının 8’i de yine yanlış derleme kaynaklıydı
- Kalan 2 vaka, hata raporlama kodundaki use-after-free idi
roc checkveroc bundlehata mesajlarında dosya adları U+FFFD replacement character olarak bozuluyordu- Rust borrow checker kullanılsaydı bu iki hata da yakalanabilirdi
- Farklı araç seçiminin gerçek kullanıcıya etkisi şöyle olurdu
| Araç seçimi | Gerçek bellek güvenliği etkisi |
|---|---|
Zig ReleaseFast |
Bazı hata mesajlarında dosya adlarının görüntülenmemesine yol açan 2 hata |
Zig ReleaseSafe |
Bazı hataların panic vermesine ve dosya adlarını görüntüleyememesine yol açan 2 hata |
| Rust borrow checker | İki hatayı da önlerdi |
- 18 ay, yüz binlerce satır kod ve yüzlerce hata raporu dikkate alındığında, bu üç seçenek arasındaki fark proje açısından pratikte büyük değildi
- Bun, JavaScript’in izlemeli garbage collection değerleriyle elle yönetilen değerleri birlikte işlerken use-after-free, double-free ve serbest bırakmama türü hataların büyük paya sahip olduğunu gördü
- Roc derleyicisi JavaScript ya da başka bir izlemeli garbage collector ile entegre olmadığı için aynı yaşam süresi yönetimi sorunlarını yaşamıyor
- Roc’un, derleyici içi bellek hatalarından çok üretilen çıktı kodundaki bellek hatalarını bulacak araçlara ihtiyacı var; ikincisi zaten borrow checker’ın kapsamı dışında kalıyor
Build süresindeki gerçek sonuçlar
zig build --watch -fincremental, şu anda yaklaşık 450 bin ila 460 bin satırlık Zig kodunda değişiklikleri yaklaşık 35 ms içinde yeniden derliyor- Kararlı Zig 0.16.0 sürümünde, Roc kod tabanında
-fincrementalözelliğini bozan bir hata bulunuyor- Düzeltme eklenmiş durumda, ancak bunu kullanmak için uyumluluğu bozan Zig 0.17.0 ön derlemesine geçmek gerekiyor
- İlgili bağımlılıkların da birlikte vendor edilip 0.17.0’a yükseltilmesi gerektiğinden, bir sonraki kararlı sürüm bekleniyor
- Intel masaüstü ve Ubuntu 26 üzerinde ölçülen sonuçlar şöyle
| Roc derleyicisi | Kod boyutu | Soğuk build | Artımlı build |
|---|---|---|---|
| Rust 1.85.0 tabanlı özgün sürüm | 354K | 32,4 sn | 10,0 sn |
| Rust 1.97.0 tabanlı özgün sürüm | 354K | 25,4 sn | 3,4 sn |
| Zig 0.16.0 işlev eşdeğerliği noktasındaki sürüm | 320K | 39,6 sn | 8,6 sn |
| Zig 0.17.0 tabanlı güncel yeniden yazım | 464K | 32,1 sn | 0,035 sn |
- İşlev eşdeğerliği noktasında neredeyse hiç değişmeyen çıktılar da her seferinde yeniden derleniyordu; şimdi ise yalnızca gerektiğinde üretiliyorlar
- Bu değişiklik kodu yaklaşık %50 büyütmesine rağmen soğuk build süresini hızlandırdı
- Rust 1.85.0’dan 1.97.0’a geçerken artımlı build süresi 10 saniyeden 3,4 saniyeye indi; yani 18 ayda yaklaşık üçte iki oranında iyileşme sağlandı
- Zig’in 35 ms’lik sonucu, 3,4 saniyenin yaklaşık yüzde biri düzeyinde ve Rust ölçümündekinden yaklaşık %50 daha büyük bir kod tabanında elde edildi
- Şu anda
-fincrementalyalnızca x86-64 üzerinde çalışıyor; katkı verenlerin çoğu ARM tabanlı Mac kullandığı için build süresi avantajından henüz tam olarak yararlanılamıyor
İşaretçisiz yapı ve parse etmeden deserileştirme
- Yeni disk önbelleği, Zig derleyicisinde ve oyun geliştirmede kullanılan yaklaşımda olduğu gibi, yürütme açısından verimli bellek yerleşimini doğrudan disk biçimi olarak kullanıyor
- Tüm derleyici veri yapıları işaretçiler yerine 32 bit indeks dizileri ile ifade ediliyor ve birçok yerde struct dizisi biçimi kullanılıyor
- Bellek kullanımını azaltıyor ve çalışma hızını artırıyor
- Ayrı bir serileştirme biçimine dönüştürmeden veri yapılarını doğrudan diske yazabiliyor
- Deserileştirme sırasında disk baytları parse edilmiyor
- Baytlar belleğe okunuyor
- Mevcut veri yapılarının yeni dizileri işaret etmesi için bazı yeniden yerleştirmeler yapılıyor
- Sonrasında hemen kullanılabiliyor
- Hız, pratikte diskteki baytları belleğe okuyan I/O hızıyla sınırlı
- Veri işletim sistemi disk önbelleğinde varsa, önceki build sonuçları yaklaşık
memcpyhızında yükleniyor
- Veri işletim sistemi disk önbelleğinde varsa, önceki build sonuçları yaklaşık
roc check, ilk çalıştırmada parse etme, tip denetimi vb. sonuçları diske kaydediyor- Girdi kaynak kodu değişmediyse ikinci çalıştırmada veri yapıları doğrudan diskten belleğe taşınıyor
roc test, deterministik saf fonksiyon test sonuçlarını da önbelleğe alıyor- Önbellek dosya düzeyinde çalıştığı için bir dosya değişirse yalnızca o dosya ve ona bağımlı dosyalar yeniden işleniyor
- Bu yaklaşım, derleyicinin tamamı işaretçiler yerine indeksler kullandığı için mümkün; genel işaretçi merkezli yapılarda parse etmeden deserileştirme mümkün değil
-
İndeks tabanlı yapının güvenlik sınırları
- Bir işaretçinin yanlış adresi göstermesi gibi, bir indeks de yanlış diziyi sorgulayıp rastgele baytlar okuyabilir
- Rust borrow checker işaretçi ömürleriyle ilgilenir, ancak hangi indeksin hangi diziye ait olduğunu denetlemez
- Gerekli dizi sayısı önceden biliniyorsa Rust'un
compact_arenaaracı, macro ile type tag üreterek yanlış dizi sorgularını engelleyebilir - Roc'ta olduğu gibi modül sayısına göre dizi sayısı değişiyorsa bu teknik uygulanamaz; bu yüzden
compact_arenadaSmallArena::newişleviniunsafeolarak işaretler - Boş bir arena oluşturmak tek başına risk yaratmaz, ancak gerçek risk çok sık yapılan dizi indeksleme işlemlerindedir
- Safe Rust,
unsafeküçük ve izole olduğunda etkili olur; ancak Roc'taki gibiunsafeyaygınsa bu varsayım geçerli olmaz
Roc için uygun olan Zig ekosistemi
- Bun'da JavaScript ile manuel belleğin birlikte çalışması nedeniyle, temizleme kodunu yalnızca bir kez çalıştıran Rust
Dropözelliği faydalıydı - Roc ise bunun tersine, modül ve derleme aşaması başına ayrı arena kullanmak istediği için, global allocator ve örtük
Dropvarsayımıyla yazılmış Rust paketleri kullanışsız kaldı - Zig ekosisteminde allocator'ı açıkça geçiren API'ler yaygın olduğundan, Roc'un bellek yönetimi yaklaşımıyla iyi uyuştu
- Rust ekosistemi Bun'ın istediği yapıya, Zig ekosistemi ise Roc'un istediği yapıya daha uygundu
-
LLVM bitcode serileştiricisini yeniden kullanma
- LLVM, Roc optimizer'ının temel bağımlılığı ve Roc'un kendi optimizasyonlarından sonra ek optimizasyonlar yapıyor
- LLVM, ana API uyumluluğunu sık sık bozduğu için sürüm yükseltmeleri önemli zaman ve maliyet gerektiriyordu
- LLVM'in serileştirilmiş bitcode biçimi kararlı ve geriye dönük uyumlu olduğundan, kendi serileştiricisini kullanmak C++ API değişikliklerinden bağımsız olmayı sağlıyor
- Bunun için LLVM C++ kütüphanesinden ayrılmış, elle yazılmış bir bitcode serileştiricisi gerekiyor
- Bilinen mevcut uygulama Zig derleyicisindeydi ve Roc'un yeni derleyicisi bu Zig kodunu yeniden kullanıyor
- Roc'un Zig ekosisteminden aldığı en büyük bağımlılık kaynağı, genel paketlerden çok Zig derleyicisinin kendisi
Zig'de özlenen Rust özellikleri
- Derleyici geliştirmede açık allocator denetimi gerekli olsa da, testlerde Rust'un otomatik allocation/deallocation yaklaşımı daha rahattı
- Zig test allocator'ı bellek sızıntılarını bulabiliyor ve derlenmiş Roc kodundaki sızıntıları da tespit edebiliyor
- Buna karşılık her testte
initvedefer deinitdoğru yazılmalı; tek bir hata bile testin sızıntı nedeniyle başarısız olmasına yol açıyor
- Zig'in
comptimeözelliği parametrik ve ad hoc çok biçimlilikle örtüşse de, bu iki çok biçimlilik türü özleniyor- Rust'taki
Allocatortrait'iselfalabiliyor - Zig'deki
ArenaAllocatorgibi uygulamalar iseanyopaqueişaretçisi alıp bunu kendi türüne cast etmek zorunda
- Rust'taki
- Özel struct alanları olmadığından, doğrudan erişilmemesi gereken alanlara erişim derleme hatasıyla engellenemiyor
- Kod farkı incelemelerinde yalnızca alan erişimleri görünüyor, asıl struct'ın dokümantasyonu görünmediği için her seferinde ayrıca kontrol etmek gerekiyor
- Fonksiyonların, değişkenlerin ve sabitlerin hepsinde
snake_casekullanan Rust tutarlılığı bazen özleniyor unsafeve borrow checker'ın bir maliyeti vardı, ama belirli sorunları yalnızcaunsafeblokları içinde düşünmek zorunda olmanın verdiği bir güven de sağlıyordu- Bu, Zig'e aynı özelliğin eklenmesi gerektiği anlamına gelmiyor
- Zig'de, Rust'a kıyasla ölü kod daha geç fark ediliyordu
- Zig'in yerleşik araçları ve TigerBeetle'ın
tidy.zigaracı da bazı ölü kodları yakalayamıyor - Ölü kod binary'ye üretilmediği için kullanıcıyı etkilemiyor, ancak kod tabanını yönetmeyi zorlaştırıyor
- Zig'in yerleşik araçları ve TigerBeetle'ın
- Rust'ta minor sürüm yükseltmeleri ve edition değişimleri çoğunlukla sorunsuzdu
- Zig şu anda geriye dönük uyumluluğu hedeflemiyor; bu beklenen bir durum olduğu için büyük sorun değil, ama Rust'un basit yükseltme deneyimi daha iyiydi
Zig'de memnun kalınan yönler
- Zig, fonksiyonel programlamadaki gibi tanıdık araçları azaltıp bunun karşılığında başka özellikler kazanan çıkarımsal tasarımın çekiciliğine sahip
- Macro yok ve parametrik çok biçimlilik dahil birçok sorun
comptimeya da normal fonksiyonlarla çözülebiliyor - Veri yerleşimi ayrıntılı biçimde kontrol edilebiliyor
u7,u5gibi 2'nin kuvveti olmayan tamsayı türleri ek bit işlemi olmadan kullanılabiliyor- packed struct için yerleşik destek var
- Fonksiyonlar tanım yerinde değil, çağrı yerinde inline edilebiliyor
- Rust'ta macro tabanlı crate gerektiren özellikler ek bağımlılık olmadan kullanılabiliyor
- Zig build toolchain'i, Alpine Linux ve WebAssembly için bağımsız çalıştırılabilir binary üretimine çok uygundu
- Roc standart kütüphanesine karşılık gelen builtins'i opak binary blob olarak derleyip nihai çalıştırılabilir dosyaya ekleyen özel build sürecini de yönetebildi
- Uber de Zig dilini kullanmadan build altyapısında Zig toolchain'ini kullanıyor
- Zig'in hata işleme modelinde, başarısız heap allocation bile sıradan kullanıcı alanı hatası olarak ele alınıyor
- Roc da anonim sum type'lar ve payload'lar kullanarak hataların doğal biçimde birikmesini sağlayan benzer bir strateji kullanıyor
- Rust'un
anyhow,thiserrorve standartResulttabanlı yaklaşımına kıyasla Zig ve Roc'un yaklaşımı tercih ediliyor - Hata yayma sözdiziminde Zig'in
tryyapısından çok Rust'un sonek?operatörü tercih edildiği için Roc da sonek?operatörünü benimsiyor - Ayrıntılı allocator API'leri ve yüksek performanslı derleyiciler için yeniden kullanılabilir kodlar dahil, proje genelinde Zig seçimi konusunda büyük memnuniyet var
Roc'un sonraki adımları
- Yeni derleyicinin 0.1.0 sürümünün bu yılın ilerleyen dönemlerinde yayımlanması planlanıyor; bu, Roc'un ilk numaralandırılmış sürümü olacak
- Yayımdan önce de Nightly build'ler denenebilir, ancak şu anda çeşitli hatalar, tamamlanmamış özellikler ve eksik dokümantasyon bulunuyor
- Roc Programming Language Foundation, ABD'de 501(c)(3) statüsünde bir kâr amacı gütmeyen kuruluş ve bağışlar ağırlıklı olarak katkıda bulunanlara ödeme yapmak için kullanılıyor
- Gelecekteki geliştirme ilerleyişi ve sorular için Roc Zulip takip edilebilir
2 yorum
Lobste.rs yorumları
Sayılara bakınca Zig’in 35 ms’i, Rust’ın 3,4 saniyesinden neredeyse 100 kat hızlı ve kod da yaklaşık %50 daha fazla; ancak gerçek geliştirme sürecinde bu fark biraz abartılı görünüyor
Derleyiciyi her dakika yeniden derlemek yerine 10 dakikada bir derliyorsanız kazanılan süre yaklaşık 3 saniye olur; 3,4 saniye de yeterince hızlı
Kod tabanı büyüdükçe Rust yavaşlayabilir, ama soğuk derleme süresi aksine kötüleşmiş; ayrıca iki yılda bir dizüstü bilgisayar değiştirildiğini ve derleyicinin kendi iyileştirmelerini de hesaba katınca Rust’ın derleme süresinin uzun vadede sürekli artacağını kesin söylemek zor
Rust’ın
checkmodu ve LSP’si tam yeniden derlemeden hızlı, ama aynı seviyede değil--watch -fincrementalkombinasyonunu kullanınca her kayıtta yeniden derleme yapıp çok hızlı ve sık geri bildirim alınabiliyorKişisel projemi Rust’tan Zig’e taşıdım ve Rust’ı da yıllarca işte kullandım; Zig’in derleme hızı gerçekten nefes aldıran bir değişimdi
35 ms inanılmaz hızlı
Sadece yeniden linkleme bile bundan uzun sürecekmiş gibi geliyor; derleyicinin gerçekte ne yaptığını merak ediyorum
Bir fonksiyonun implementasyonunu değiştirirseniz yeni assembly’yi mevcut ikiliye eklemek yeterli olabilir; ancak inlining, fonksiyon imzası değişiklikleri, bağımlılıklar, yer yetersizliğinde yeniden konumlandırma gibi durumlar ek analiz gerektirir
Bir fonksiyonu değiştirir veya eklerseniz çıktı yürütülebilir dosyasının
.textbölümünde yeterince büyük boş bir alan bulunup yeni makine kodu yazılırYer yetmezse
.textgenişletilip başka verilerin taşınması gerekebilir; ancak bölüm üstel olarak genişletilerek maliyet zamana yayılırGerekirse sembol tablosu ve yeniden konumlandırma girdileri de eklenir; bunlar için de önceden boş alan bırakıldığı için işlemden sonra dosyayı kapatmak yeterlidir
Yakın zamanda gördüğüm Tracy sonuçlarına göre tahmin edersem toplam 35 ms içinde linker’ın kullandığı süre yaklaşık 1 ms civarında
300 bin satırlık Rust kodunda
unsafe’in yaklaşık 1.200 kez kullanılmış olması beklediğimden fazlaÖrneğin Inko’da yalnızca 162 adet
unsafe { ... }ifadesi ve C ABI üzerinden üretilen koda açılan 87 adetunsafefonksiyon var; toplam Rust kodu ise yaklaşık 88 bin satırAncak Inkwell’in çalışma biçimi nedeniyle LLVM backend’in tamamı fiilen devasa bir
unsafealanı olduğundan, basit bir karşılaştırmayla sonuca varmak zorBen yalnızca pointer’ları doğrudan ele almak gibi gerçek bellek güvenliğini zedeleyen fonksiyonları
unsafeolarak işaretliyorum; başka geliştiriciler ise panikten kaçınmak için belirli bir işlemin önce yapılması gerektiğine dair ipucu olarak da işaretleyebiliyorRust standart kütüphanesi ve derleyicisinde
unsafe’in 40 bin kez geçtiği bilgisi doğru değilBu, testlerde ve yorumlarda geçen kelimelerin de sayıldığı bir sonuç; çoğu standart kütüphanede, derleyicinin kendisinde ise yorumlar ve testler dahil edilse bile 2 binden az
rustc’ye katkı verirken PR’larım içinde
unsafeiçerenlerin oranı %1 bile değildi; derleyici içindeki güvenli olmayan kod bu kadar nadirdirRust’ın tamamının dayandığı temel runtime’ı uygulayan standart kütüphanede
unsafe’in çok olması doğal; bu tür kod her dilde, açık ya da örtük biçimde, güvenli değildirSöz konusu derleyici rustc’den yaklaşık 10 kat küçükken
unsafekullanımı benzer seviyede; bu yüzden her yere yayılmış demek zor ama sıkça karşınıza çıkıyor ve rustc’den neden çok daha fazla gerektiğini merak ediyorumİki kod tabanında da standart kütüphane ile ilgili
unsafekullanımlarını ayrıca analiz etmedik; ancak yeni derleyiciyi Rust ile implemente etseydik, önbellekleme ve pointer yerine indeks kullanan yapı nedeniyle rustc’den daha fazlaunsafeolacağını tahmin ediyorumRust ekibinin çalışmalarını küçümseme niyetimiz yoktu; başka projelerin başarılarına saygı gösterirken kendi seçimlerimizi ve ilerleme durumumuzu anlatmak istemiştik
Rust ve Zig’in artılarını ve eksilerini saklamadan dengeli biçimde değerlendiren bir yazı; 18 ay boyunca yüz binlerce satır yazıp yüzlerce hatayı ele aldıktan sonra bile seçenekler arasından başka bir dil seçilseydi proje sonucunun büyük ölçüde değişmeyeceği sonucuna varması özellikle dürüstçe
Ancak Roc ya da rustc gibi makine kodu üreten derleyicilerde bellek açısından güvenli olmayan işlemlerin işin büyük bir kısmı olduğu açıklamasını anlamak zor
OCaml veya Haskell ile yazılmış çok sayıda derleyici de var; makine kodu üretmenin kendisi baytları bir vektörde oluşturup dosyaya yazmak olduğundan güvenli olmaması için bir neden yok
Yorumlama ya da JIT derleme söz konusuysa anlayabilirim, ama genel derlemede de bunun neden gerekli olduğunu merak ediyorum
Tehlike üretilen makine kodu çalıştırıldığında ortaya çıkar; gerçek derleyiciler çoğu zaman makine kodu üretirken aynı zamanda onu çalıştırdığı için bunu işin büyük bir kısmı diye ifade ettim
Yorumlama ve JIT dışında, Rust’ın
const fn’i ya da Roc’ta en üste taşınabilen ifadeler gibi derleme zamanında kullanıcı kodunu değerlendirmek ya da testleri çalıştırdıktan sonra çıktıları inceleyip kullanıcıya gösterilecek içeriğe karar vermek gibi işler de buna girerBozulan bellek ister derleyici sürecinde ister üretilen programda olsun, işlemcinin yanlış davranmasının nedeni derleyicinin ürettiği komutlardır ve düzeltilmesi gereken yer de derleyici kodudur ölçütü kullanılmış
Harici bağlantı kodu ve çöp toplayıcı gibi bileşenler Rust’ta da çok miktarda
unsafegerektiriyor; bu da güvenli Rust’ın avantajlarını önemli ölçüde dengeliyorYalnızca derleyiciyi güvenli bir dille yazmak mümkün, ancak performans sorun oluyor; Zig ve Roc derleyicisi dizi yapısı (SoA) ve işaretçiler yerine dizi indekslerini yaygın biçimde kullanıyor
Bunu Rust ile uygulayınca ödünç alma denetleyicisini aşmak gerekiyor ve ilgili güvenlik avantajları kaybediliyor
Derleyicinin yaşam süresi ilişkileri şaşırtıcı derecede basit: bir aşamada verileri arenaya ayırıp oluşturup değiştirebilir, sonraki aşamaya salt okunur olarak aktarabilir ve ardından tüm arenayı atabilirsiniz
Daha karmaşık kısımlar, diskten durum okuyup ikili dosyayı yerinde değiştiren artımlı derleme ve bağlamadır; bu süreçte durum bozulması, hatalar, yanlış derleme ve bellek sorunları ortaya çıkabilir, ancak bunlar derleyici sürecinin kendi bellek güvenliğinden ayrıdır
Güvenlik ve doğruluk, program içindeki bellek güvenliğinden çok daha geniş kavramlardır; özellikle Rust ile ifade edilmesi zor işleri güvenli ve doğru biçimde yapmak söz konusu olduğunda bu daha da böyledir
compact_arena’nın geliştiricisi olarak anılmak sevindirici, ancak bu kütüphaneninunsafekullanım biçimi yanlış tasvir edilmişcompact_arena’nın amacı, güvenli Rust’tanew’in yanlış kullanılmasını engellemeyi garanti ederek indekslemeyi güvenli hale getirmektirmk_arenamakrosu, gerekli arena sayısının bilinmediği döngüler içinde bile güvenli Rust kodundan çağrılabilirnew’in güvenli biçimde kullanıldığı açıklamasını anlamamış olabilirim, ancak https://docs.rs/compact_arena/0.5.0/… adresindeki güvenlik bölümünde, oluşturucuya geçirilen etiketin indeksleme tekniğinin temeli olduğu ve bu değer başka bir arenada kullanılırsa iki arenanın indekslerinin karışıp sınır dışı erişime ve tanımsız davranışa yol açabileceği yazıyorBenim vurgum
new()çağrısının kendisi değil,new()ile oluşturulan değere indeksleme yaparken riskin ortaya çıktığıdır; denetlenmesi veunsafeolarak işaretlenmesi gereken yerin de gerçek indeksleme çağrı noktası olduğunu düşünüyorumYine de tasarımı yanlış anlamış olmam mümkün
Python’da yeni kodun yansıtılması için programı yeniden başlatmak gerektiğini, hot reloading içinse standart dışı uzantılar ve dikkatli kod yazımı gerektiğini biliyorum
Hot reloading, Lisp veya Smalltalk gibi imaj tabanlı geliştirme ortamlarında ya da Erlang’da daha yaygındır
importlib.reload()kullanılırsa Python’da da hot reloading mümkün, ama ne kadar yaygın kullanıldığını bilmiyorumcargo buildkullanılmazsa derleme süresinin azaltılıp azaltılamayacağını merak ediyorumBazel’i yapılandırma işinin Zig’e taşımaktan daha büyük mü küçük mü olduğu belirsiz; elbette derleme süresi dil değiştirmenin tek nedeni değildi
Hacker News yorumları
Yazının geneli fena değil, ancak Roc veya rustc gibi makine kodu üreten derleyicilerde bellek açısından güvensiz işlemlerin işin büyük bir bölümü olduğu ifadesine katılmak zor.
Çalışma sırasında ikili dosya yamalama veya kodu yeniden yükleme için güvensiz kod gerekebilir, ama sıradan biçimde yürütülebilir dosya üretme sürecinde makine kodu üretiminin kendisinin güvensiz olmasını gerektiren bir neden yok.
Aksine güvensiz kodla karşılaşma olasılığı dilin çalışma zamanında daha yüksek.
Ancak pratikte birçok derleyici makine kodu üretirken onu doğrudan da çalıştırdığı için “işin büyük bir bölümü” diye ifade ettim; fakat bir derleyicinin bu iki işi de mutlaka yapması gerekmez.
İkili dosya yamalama, kodu yeniden yükleme ve çalışma zamanının yanı sıra Rust’taki
const fnya da Roc’ta en üste taşınabilecek ifadeler gibi derleme zamanında kullanıcı kodunu değerlendirme, testleri çalıştırıp çıktıyı denetleyerek ne gösterileceğine karar verme gibi işleri kastetmiştim.Bu, emniyet kemeri taksanız da başınızı çarpabilirsiniz, dolayısıyla emniyet kemeri yalnızca zahmettir ve kullanılmamalıdır demeye benziyor.
Uç düzey yüksek performanslı kodlarda veri yapıları, algoritmalar ve bellek ayırma stratejileri baştan değişir; TigerBeetle da başlangıçta tüm belleği tek seferde ayırmasıyla bilinir.
Roc derleyicisi de Zig’e benzer bir uzlaşma seçmeye çalıştığı için birçok ortak örüntünün görülmesi doğal.
ReleaseSafein çalışma zamanı denetimleriyle serbest bırakıldıktan sonra kullanımı (use-after-free) yakaladığı iddiası için bir dayanak görünmüyor.Zig belgelerinde çalışma zamanı bellek güvenliği denetimlerini araştırdım;
use-after-free,UaF,safety-checkeddiye aratsam da ilgili bir şey bulamadım. Ayrıca sürüm derlemesindeDebugAllocatorkullanılsa bile bunu güvenilir biçimde tespit edemiyor.İlgili yazıyı https://landaire.net/memory-safety-by-default-is-non-negotia... adresinde derledim.
Bu, yapay zekâdan önce bizzat kod yazarak edindiğim bir kanaat; Zig ile LLM birleşiminde bir şeyler değişebilir, ama yazıyı okudukça tuhaf ifade edilmiş daha fazla iddia görüyorum ve güvenmek zorlaşıyor.
Samimi bir teknik değerlendirmeden çok mevcut tartışmayı meşrulaştırmak için yazılmış gibi okunuyor; yine de sıra dışı yazıları ve dilleri sevdiğim, aşırı yapay zekâ furyasına da mesafeli olduğum için şimdilik iyi niyetle bakmaya çalışıyorum.
ReleaseSafein sınır denetimi eklediğini ve ulaşılamaz kodda panic oluşturduğunu biliyorum.Zig zamansal bellek güvenliği (temporal memory safety) sunmuyor gibi görünüyor.
Olgun OCaml’in esnek ve yeterince ifade gücü yüksek olduğu için prototip doğrulamada kullanılıp nihai uygulama dili olarak seçilmemesi ilginç.
Zig’in artımlı derlemesinin dune’dan anlamlı ölçüde hızlı olup olmadığı da şüpheli; çapraz derleme bir avantaj olmasına rağmen “neden Zig” kısmında ele alınmamış.
Bir derleyicide ince ayarlı bellek denetiminin gerçekten önemli olup olmadığını; OCaml ile başlamış Rust ve WASM’de olduğu gibi hangi dönüm noktasında bakımcıların başka bir dile geçmeye karar verdiğini de merak ediyorum.
Zig’in artımlı derlemesi açıkça belirleyici bir özellik; kısa vadede bunu elde etmek için dili değiştirme tercihi anlaşılır.
Ancak orta vadede Rust’a da yakın gelecekte benzer bir özelliğin ekleneceğini umuyorum.
Hız istiyorum ama hızlı giderken ayağıma kurşun sıkmak istemiyorum; Rust’ın güvenliğini, Zig’in özelliklerini ve çöp toplama içermeyen Go çalışma zamanını birleştiren bir dili kendim yapıyorum.
Bu daha uygulanabilir görünüyor ve kullanıcı alanında gerçekleştirmek de mümkün gibi.
Derleyicinin uygulanacağı dili seçme sürecinde gerçekten bilimsel bir karşılaştırma yapılmış olsaydı çok daha ikna edici olurdu
Yüksek performanslı bir derleyici için düşük seviyeli bir sistem dili gerektiği yönündeki doğrulanmamış varsayımdan (https://www.roc-lang.org/faq#self-hosted-compiler) yola çıkıp, Rust dışındaki tek seçeneğin Zig olduğu sonucuna varmış gibi görünüyor
Derleyici performansında belirleyici olan algoritmalardır; hızlı yönetimli dillerde bile aynı algoritma kullanılıyorsa çalışma süresi genellikle iki katın içinde kalırken, algoritma farklarından kaynaklanan performans uçurumunun bir sınırı yoktur
Zig’in kendisi, derleyiciyi düşük seviyeli bir dille yazmanın onu hızlandıracağı teorisine karşı bir örnektir; Roc’un saniyede yaklaşık 15 bin satırı da hızlı sayılmaz. 1998’de bile ML derleyicilerinin saniyede 3 bin satır işlediğini gösteren bir kaynak var (https://flint.cs.yale.edu/cs421/case-for-ml.html)
Mevcut derleyici çalışmasını durdurup, 10 bin satırdan kısa olabilecek küçük bir Roc alt kümesi için kendi kendini derleyen bir derleyici yapmak gelecekte daha faydalı olabilir
Böylece 300 bin satırlık bir uygulama yerine 10 bin satır ölçeğinde çeşitli uygulamalar denenebilir ve düşük seviyeli bir dilin gerçek performans hedefleri için gerekli olup olmadığı doğrulanabilir
Kendi kendini derleme sürecinde Roc’un gerçekten önemli özellikleri ortaya çıkar, daha fazla Roc kodu yazılmış olur; derleyicinin ihtiyaç duyduğu genel amaçlı özellikler iyileştirildiğinde alt uygulamalar da birlikte daha iyi hale gelir
1990’larda ML’in hızlı derlenmiş olması, bugünkü Roc’un derleme hızını yargılamak için tek başına yeterli değil. Çünkü dil tasarımı gereken algoritmalara güçlü kısıtlar getirir ve modern donanım da çok daha karmaşıktır
Roc’ta belli ölçüde overloading var ve closure’ları heap üzerinde ayırmamak için gelişmiş algoritmalar da var gibi görünüyor; bu tür gereksinimler ortadan kaldırılamayan algoritmik karmaşıklık yaratabilir
Algoritma optimizasyonunun sınırına ulaşıldığında geriye sabit katsayıları azaltmak kalır; özellikle belleği yöneten yüksek seviyeli diller, bunun ne kadar düşürülebileceği konusunda belirgin bir alt sınır oluşturur
Gerçek kodda bellek yerleşimini doğrudan kontrol ederek performansın 10 kattan fazla iyileştiği durumlar gördüm; oyun sektöründe bu tür işler kariyerin büyük bir bölümünü bile oluşturabiliyor. Akıllı bir algoritmanın tüm performans sorunlarını ortadan kaldırdığı bir ortam gerçeklikten uzak
Rust build’leri, her bilgisayarda depolama alanının ciddi biçimde boşa harcanmasının nedenlerinden biri; birkaç kütüphane derlendiğinde onlarca GB hızla birikiyor
Bağımlılıkların projeler arasında yeniden kullanılmasını sağlamak için global bir build klasörü ayarlanabilir, ancak hangi çözüm olursa olsun bunun varsayılan davranış olarak sunulması daha uygun olur
Ancak cache garbage collection gerekli ve bunu kolaylaştıracak yeni ara build çıktısı yerleşimi çalışması son aşamada
node_modulesboyutunun sürekli eleştirilmesini düşününce ilginç bir tezatFrontend’den çok daha az backend kodu içeren Tauri projelerinde bile Rust build çıktıları 9GB,
node_modulesise yalnızca 550MBRoc’u C ABI’ye gömülen bir betik dili olarak anlıyorum; gerçek kullanım alanlarını merak ediyorum
Büyük bir Roc platformu sağlayan eklenti ortamlarında WASM ile mi rekabet etmeyi hedefliyor; eklenti geliştiricilerin herhangi bir dili kullanabildiği WASM yerine uygulama geliştiricisinin neden bir Roc katmanı açmak isteyeceğini bilmek isterim
Küçük bir Roc platformu kullanan uygulama düzeyi bir dilse, sunucu tarafı HTTP kodunda Gleam’le, istemci kodunda Elm’le rekabet etmeyi mi hedeflediğini de merak ediyorum
Derleme süresi çok hafife alınan bir unsur. C++ build’lerinin 10 dakika sürmesini beklemek en büyük şikâyetim; geliştirme odağını tamamen koparıyor
.rsdosyasından.tsdosyasına geçince bilgisayarın kendisini değiştirmişim gibi bir fark hissediliyorZig’i doğrudan kullanmıyorum ama bazı olasılıkları için oldukça heyecanlıyım
C’ye göre kullanımı daha kolay olan Zig ile yazılmış yeni oyunlar, TigerBeetle’da potansiyelini zaten göstermiş dağıtık yazılımlar ve kişisel olarak ilgi duyduğum robotik alanı özellikle umut verici