- Rust’ın bağımlılık yönetim sistemi geliştirmeyi kolaylaştırsa da, bağımlılıkların sayısı ve kalitesi düşündürüyor
- İyi kullanılan bir crate bile güncel olmayabilir; bu yüzden bazen doğrudan kendin yazmak daha iyi olabilir
- Axum, Tokio gibi popüler crate’ler eklendikten sonra, bağımlılıklar dahil toplam kod satırı sayısı 3,6 milyona ulaşıyor ve bunu yönetmek zorlaşıyor
- Gerçekte benim yazdığım kod yalnızca yaklaşık 1.000 satır, ancak etrafındaki kodu fiilen gözden geçirmek ve denetlemek mümkün olmuyor
- Rust’ın standart kütüphanesinin genişletilip genişletilmemesi ve temel altyapının nasıl uygulanacağı konusunda net bir çözüm yok; topluluğun tamamı performans, güvenlik ve bakım arasında dengeyi birlikte düşünmek zorunda
Rust bağımlılık sorununun genel görünümü
- Rust benim en sevdiğim dil ve topluluğu ile dilin kullanılabilirliği çok güçlü
- Geliştirme verimliliği yüksek, ancak son zamanlarda bağımlılık yönetimi konusunda endişelerim oluştu
Rust crate’leri ve Cargo’nun avantajları
- Cargo ile paket yönetimi ve derleme işlerinin otomasyonu mümkün olduğu için verimlilik ciddi biçimde artıyor
- Farklı işletim sistemleri ve mimariler arasında geçiş kolaylaşıyor; dosyaları elle yönetmek veya derleme araçlarını yapılandırmak gerekmiyor
- Ayrı bir paket yönetimi derdi olmadan doğrudan kod yazmaya başlanabiliyor
Rust crate yönetiminin dezavantajları
- Paket yönetimine daha az dikkat edildiği için istikrar ve güvenilirlik geri planda kalabiliyor
- Örneğin dotenv crate’ini kullandım, ancak bakımının durduğunu bir Security Advisory üzerinden öğrendim
- Alternatif crate olarak
dotenvy’yi düşündüm, ama sonunda gerçekten gereken kısmı yaklaşık 35 satırda kendim yazdım
- Birçok dilde paketlerin bakımsız kalması sık görüldüğü için, sorunun özü bağımlılıkların kaçınılmaz olduğu durumlar
Bağımlılıkların yol açtığı kod hacmi patlaması
- Tokio, Axum gibi Rust ekosisteminin önemli ve kaliteli paketlerini kullanıyorum
- Bağımlılık olarak Axum, Reqwest, ripunzip, serde, serde_json, tokio, tower-http, tracing ve tracing-subscriber eklendi
- Ana amaç web sunucusu, dosya açma ve log işlevleri olduğu için projenin kendisi aslında basit
- Cargo vendor özelliğini kullanarak tüm bağımlı crate’leri yerel olarak indirdim
tokei ile kod satırlarını analiz ettiğimde, bağımlılıklar dahil yaklaşık 3,6 milyon satıra ulaştığını gördüm (vendor edilen crate’ler hariç yaklaşık 11.136 satır)
- Karşılaştırma için, Linux çekirdeğinin tamamının yaklaşık 27,8 milyon satır olduğu söyleniyor; yani benim küçük projem bunun yaklaşık yedide biri kadar
- Benim gerçekten yazdığım kod ise yalnızca yaklaşık 1.000 satır
- Bu kadar çok bağımlılık kodunu izlemek ve denetlemek pratikte imkânsız
Çözüm üzerine düşünceler
- Şu an için ortada net bir çözüm yok
- Bazıları Go’daki gibi standart kütüphaneyi genişletelim diyor, ancak bu da bakım yükü gibi yeni sorunlar doğuruyor
- Rust yüksek performans, güvenlik ve modülerlik hedefliyor; gömülü sistemler ve C++ ile rekabet etmeyi amaçladığı için standart kütüphanenin genişletilmesi dikkatle ele alınmalı
- Örneğin Tokio gibi gelişmiş bir runtime bile Github ve Discord üzerinde çok aktif biçimde sürdürülüyor
- Gerçekçi olmak gerekirse, asenkron runtime veya web sunucusu gibi temel altyapıları doğrudan kendin yazmak bireysel geliştiriciler için fazla ağır
- Büyük bir hizmet olan Cloudflare bile tokio ve crates.io bağımlılıklarını olduğu gibi kullanıyor; bunları ne kadar sık denetlediği ise belirsiz
- Clickhouse da ikili dosya boyutu ve crate sayısıyla ilgili sorunlardan söz ediyor
- Cargo ile nihai ikili dosyaya dahil olan kod satırlarını tam olarak belirlemek zor ve platforma göre gereksiz kodların da dahil olması gibi sınırlamalar var
- Sonuçta, yanıtı tüm topluluğun birlikte aramak zorunda olduğu bir durumla karşı karşıyayız
3 yorum
Trivy ile tarayınca js NPM ya da Java Maven’e göre high veya critical seviyeleri çok daha az çıkıyor ve daha güvenli görünüyor; peki bu yazı Rust üzerinden tam olarak neyi savunmaya çalışıyor?
Hacker News görüşleri
import foolibile kullanılabiliyor ve içinde ne olduğuna kimse aldırmıyor. Her katmanda işlevlerin belki %5'i gerekiyor ama ağaç derinleştikçe işe yaramayan kod birikiyor. Sonunda basit bir ikili dosya 500MiB oluyor ve sırf sayı biçimlendirmek için bir bağımlılık çekilmiş oluyor. Go ya da Rust, her şeyi tek dosyada toplamayı teşvik ettiği için, yalnızca bir kısmını kullanmak isteseniz bile zor bir durum ortaya çıkıyor. Uzun vadede gerçek çözüm, aşırı ince taneli sembol/bağımlılık takibi olabilir; her fonksiyon/tip yalnızca ihtiyaç duyduğu öğeleri açıkça belirtir, tam gereken kod alınır ve kalanı atılır. Bu fikir kişisel olarak bana çok cazip gelmiyor ama mevcut sistemde bağımlılık ağacından bütün evreni içeri çekme sorununu çözmenin başka bir yolunu da göremiyorumXilem) üzerinde çalışırken feature flag ile kırpmayı denedim; ama bağımlılıkların neredeyse tamamı gereken işlevlere göre korunması gereken türdendi (vulkan, PNG decoding,unicode shapingvb.). Gereksiz bağımlılıklar çoğunlukla çok küçük şeylerdi ve yalnızcaserde_jsonküçük değişikliklerle çıkarılabildi. Daha büyük bağımlılıkları (winit/wgpuvb.) kaldırmak içinse yapısal değişiklikler gerekiyordependency hellyaşamıyorlar. Bunun sebebi standart kütüphanelerinin olağanüstü iyi olması. Standart kütüphanenin bu kadar kapsamlı olması da ancak büyük şirketlerin (Google, Microsoft) yatırım yapabildiği bir alan.odosyaları üretilir, bunlar.aarşivlerinde toplanır ve bağlayıcı yalnızca gereken fonksiyonları alırdı. Ad alanı dafoolib_do_thing()gibi yapılırdı. Şimdi ise god object deseni gibi bütün fonksiyonlar üst düzey bir nesnede duruyor;foolibiçe aktarılınca her şey geliyor. Bu durumda bağlayıcının hangi fonksiyonların gerçekten gerekli olduğunu anlaması zorlaşıyor. Buna karşılık Go'nun ölü kod temizleme yeteneği çok iyi; kullanılmayan şeyler derleme çıktısından kesiliyormin-sized-rustgibi projelerle bunu destekliyordepsdosyasına tek satır eklemekten çok daha derin bir etkileşim sağlarUnisonadlı dil bu fikre kısmen benzeyen bir yaklaşım benimsiyor. Her fonksiyon AST yapısına göre tanımlanıyor, hash tabanlı küresel kayıt sisteminden çağrılıp yeniden kullanılıyorisEven,isOdd,leftpadgibi çok sayıda küçük kütüphane parçasının dağınık biçimde bakımının yapılmasındansa, birleşik bir ekibin yönettiği büyük genel amaçlı kütüphaneler geleceğe dönük güvence ve süreklilik açısından çok daha iyitree-shakingsistemlerinden yararlanmak da bir fikirtree shakingdesteği sağlıyor--gc-sectionsgibi section splitting ile zaten çözülmüş durumdathick clientise ilk kurulum 800MB olsa bile gerçek kullanımda ağ üzerinden çok kısıtlı iletişim yapıyorsa bu sorun olmayabilir. UI tarafında işbirliği için tekrar tekrar gelen büyük bağımlılıklar da tolere edilebilirtree shakingile boyut/kod şişmesi sorunu bir ölçüde çözülebilir (sunucuda zaten pek önemsenmez). Daha ciddi sorun bağımlılık tedarik zinciri riski ve güvenlik. Şirket ne kadar büyükse açık kaynak kullanımı için onay süreci de o kadar yaygın olur. Tane boyutunu artırmak, eğer 1000 özellik 1000 ayrı NPM yazarından geliyorsa, güvenlik açısından anlamlı bir çözüm değilglobkütüphanesi basit bir globbing fonksiyonu olmalı ama yazarı komut satırı aracını da içine kattığı için büyük bir ayrıştırıcıyı bağımlılığa eklemiş. Bu da sık sıkdependency out-of-dateuyarılarına yol açıyor. Ayrıcaglobkütüphanesinin sorumluluk alanı da tartışmalı. Yalnızca string desen eşleme yapması daha esnek bir tasarım olurdu (test ve dosya sistemi soyutlaması daha kolay olurdu). Her şeyi yapan bir kütüphane isteyen kullanıcı çok ama bunun yan etkileri de büyüyor. Rust'ın da çok farklı olmayacağını düşünüyorumstdlib::data_structures::automata::weighted_finite_state_transducergibi) ve düzenli ad alanlarına sahip, "batteries included" bir yapıya kavuşmasını isterim. Dilin kendisinde sürümleme ve geriye dönük uyumluluk var, bu yüzden ileride daha da iyi olabilirglobfonksiyonu aslında dosya sistemini geziyor. String eşleme içinfnmatchvar. İdeal olan,fnmatch'i ayrı modül yapmak veglobun buna bağımlı olması.globu sıfırdan yazmaya kalkınca iş zorlaşıyor; dizin yapısı,brace expansiongibi karmaşık gereksinimler yüzünden iyi tasarlanmış fonksiyon kombinasyonları gerekiyorborrow checker, tasarım sezgisi zayıf geliştiricilere karşı bir tür kalkan işlevi gördü. Bu etkinin ne kadar süreceği belirsizglobözelliği yerleşik geliyorcapability systemgömülü olmalıydı. Örneğin bir görsel yükleme kütüphanesi tasarlarken, dosya değil yalnızca stream kabul edecek şekilde yapabilir ya da "dosya açma yetkisi yok" diye açıkça tanımlayıp tehlikeli fonksiyonların kullanımını derleme zamanında engelleyebilirdiniz. Mevcut ekosistemlerde bunu yapmak kolay değil ama düzgün uygulanırsa saldırı yüzeyini ciddi biçimde azaltabilir. Bağımlılıkları azaltma kültürüyle de bu kökten çözülemiyor; Go gibi diller de tedarik zinciri saldırılarından muaf değilSans-IO(bağımlılığın doğrudan IO yapmadığı tasarım) kültürünü daha aktif biçimde yaymak gerekiyor. Yeni bir kütüphane duyurulduğunda doğrudan IO yapmasının eleştirilmesi gibi bir kültür de yararlı olur. Elbette yalnızca topluluk incelemesi yetmez amaSans-IOilkesi yaygınlaşırsa iyi olurWUFFSadlı özel amaçlı bir dil var. FiilenHello worldbile yazdıramazsınız, string tipi bile yok. Ama güvenilmeyen dosya biçimlerini ayrıştırma konusunda çok özel amaçlı. Bu tür özel amaçlı dillere daha çok ihtiyaç var. Hızlılar ve risk taşımadıkları için gereksiz denetimleri de azaltabiliyorlarpartial trust/capabilitiesözelliklerine sahipti ama yaygın kullanılmadıkları için kaldırıldılar#![deny(unsafe_code)]ileunsafekod kullanımı derleme hatasına dönüştürülebiliyor ve bu durum kullanıcıya açıkça gösteriliyor. Tam bir zorunlu denetim değil gerçi; özellikle izin verirsenizunsafekod yine yazılabiliyor. Standart kütüphane işlevlerinifeatureflag gibi geçişli biçimde kontrol eden bir capability system hayal edilebilirunsoundnesssorunları ise çözülmelipanicüretir ve her kütüphane için capability profilleri yazıp dağıtmanız gerekir. TypeScript'te buna benzer şeylerin mümkün olduğu zaten gösterildiIO monadile bu yaklaşımı bir ölçüde gerçekleştiriyor. Doğrudan IO yapamayan fonksiyonlar bunu tip imzalarıyla belli ediyorCapslockadlı proje, Go'da buna yakın bir şey yapıyorstdlibiçine çekerseniz, merkez ekip o kodun tamamından sorumlu olur; bu da ciddi bir yönetim yükü doğururtls,x509,base64vb.) kütüphane seçimi ve yönetimi açısından sancılıunsafekod vb. yüzünden)dom0'da, her kütüphaneyi ayrı bir şablon VM'de tutmak ve iletişim için ağ ad alanları kullanmak da önerildi. Hassas sektörlerde pratik olabilirblessed.rs, standart kütüphaneye girmesi zor ama yararlı olan kütüphaneleri öneren bir liste sunuyor. Bu sistem sayesinde paketlerin çoğunun belirli amaçlarla sınırlı kalıp yönetilebilir olmasını seviyorumcargo-vetde önerilmeye değer. Güvenilir paketleri izleme ve tanımlama imkânı sağlıyor; örneğin içeri almadan önce uzman denetimi gerektiren paketler ya datokiobakım ekibinin yönettiği paketlere yarı-YOLO güven verilmesi gibi politikalar tanımlanabiliyor.blessed.rs'den daha formel ve ekip içinde resmî yarı-standart bir liste paylaşmak için iyi bir araçleftpadolayından sonra paket yöneticilerine karşı olumsuz bir algı kaldı.tokiogibi bazı şeyler fiilen dil seviyesinde özellik sayılır; eğer OP, Go'nun tamamını ya da Node'un V8'ini bile tek tek denetlemek gerektiğini söylüyorsa bu gerçekçi değiltokioda biri tarafından sürekli denetleniyor. Bunu çok kişi yapmıyor olabilir ama yine de birileri yapıyorcargonun oldukça özgün bir desteğicargopaketlerinde feature flag gerçekten çok iyi bir özellik. Gereksiz bağımlılıkları bu bayrakların arkasına taşıyan PR'lar sık sık gönderiyorum.cargo treeile bağımlılık ağacını görmek de çok kolay. İkili dosyaya gerçekten giren kod satırlarını görmek ise çok anlamlı değil; çünkü fonksiyonlar satır içi açıldığında çoğu zatenmainiçine karışıyorBu sadece Rust’a özgü bir sorun değil.
Ortak paket depoları ve geçişli bağımlılıkları destekleyen paket yöneticilerine sahip tüm dillerin ortak avantajı ve aynı zamanda potansiyel sorunu.
Sonuçta bunu alıp kullananların dikkatli kullanması gerekiyor ama…
Node&npm’in leftpad vakasına rağmen değişen hiçbir şey yok.