RustProgramlamaRust Geliştiricisi

**Rust**'in **Drop Check** (dropck) algoritmasının, generic bir yapının, daha önce serbest bırakılmış verilere erişme potansiyeli olduğunda **Drop**'u uygulamasını nasıl engellediğini analiz edin ve ham işaretçileri içeren türler için bu analizi bilgilendirmek amacıyla **PhantomData**'nın neden gerekli olduğunu açıklayın.

Hintsage yapay zeka asistanı ile mülakatları geçin

Cevap

Sorunun geçmişi: Drop Check (dropck) algoritması, generic yok edicilerin, daha önce serbest bırakılmış verilere erişebileceği erken Rust sürümlerinde bulunan bir seslilik açığını kapatmak için tanıtıldı. Dropck'ten önce, yığın üzerinde tahsis edilmiş verilere bir referans tutan bir yapı oluşturulabilir, Drop uygulandıktan sonra bu verinin, kapsayıcıdan önce serbest bırakılması sağlanarak use-after-free sorunu ortaya çıkıyordu. Bu sorun, ödünç alınmış veriler içerebilecek generic koleksiyonlarla birlikte kritik hale gelerek yok edici güvenliğini sağlamak için ihtiyatlı bir analiz gerektirdi.

Sorun: Generic bir tür Container<T> Drop'u uyguladığında, derleyici, yok edicinin geçersiz bellek alanına erişimini önlemek için T'nin kapsayıcıdan daha uzun süre yaşamasını sağlamalıdır. Ham işaretçileri (örneğin, *const T) kullanan türlerde, derleyici yaşam süresi bilgisine sahip değildir çünkü ham işaretçiler ödünç kontrolü tarafından izlenmez. Açık yaşam süre işaretçileri olmadan, derleyici yok edicinin, mevcut kapsamın sahip olduğu ve önce serbest bırakılabilecek bir veriye isabet eden bir işaretçiyi dereferanslayıp dereferanslayamayacağını doğrulayamaz.

Çözüm: PhantomData, T türünün sahipliğini veya ödünç almasını simüle eden, sıfır boyutlu bir işaretçi işlevi görür. Ham bir işaretçi tutan bir yapıda PhantomData<&'a T> dahil etmek, derleyiciye yapının mantıksal olarak 'a yaşam süresine bağlı bir referans tuttuğunu bildirmiş olur. Drop Check algoritması bunu kullanarak yapının 'a yaşam süresinden daha uzun sürmemesini sağlar. Yapı Drop'u uyguluyorsa ve referansından daha uzun sürebilecekse, derleme işlemi başarısız olur ve tanımsız davranışın önüne geçer.

Hayattan bir durum

Bir byte tamponunu saran sıfır kopyalı bir ağ protokolü ayrıştırıcısı oluşturuyorsunuz. Packet<'a>i tanımlıyorsunuz, bu Vec<u8>'nin geçici bir işaretçisi *const u8 içeriyor ve bu işaretçi ağ yığınından alınıyor. Packet için Drop'u uygulamayı deniyorsunuz ve istatistikleri güncelleyerek ham işaretçi aracılığıyla okumak istiyorsunuz. Tehlike, Vec<u8> alım fonksiyonu çıkarken serbest bırakılırken, ancak Packet daha sonra işlenmek üzere bir kuyrukta saklanabileceğidir; bu da Drop çalıştığında use-after-free yaratır.

Öncelikle, ham işaretçi yerine bir referans &'a [u8] kullanmayı düşünüyorsunuz. Bu, ödünç kontrolünü kullanarak tamponun yeterince uzun süre yaşamasını sağlıyor. Ancak bu, API'yi önemli ölçüde kısıtlıyor çünkü paketi serbestçe taşıyamıyor veya 'static sınırlarını gerektiren koleksiyonlarda saklayamıyorsunuz ve ayrıştırıcılarda yaygın olan kendine referans veren desenleri engelliyor.

İkinci olarak, tamponun sahipliğini paylaşmak için Rc<Vec<u8>> kullanmayı düşünüyorsunuz. Bu, herhangi bir paket var oldukça verinin geçerli kalmasını sağlıyor. Dezavantajı ise, referans sayımının ve yığın tahsisinin performans maliyetidir, bu da yüksek verimli ağ işlemenin sıfır kopya, sıfır üst yük gereksinimlerini ihlal ediyor.

Üçüncü olarak, performansı korurken yaşam süresi bağımlılığını işaretlemek için PhantomData<&'a ()> eklemeyi düşünüyorsunuz. Ancak bu, Drop'un burada temelde güvensiz olduğunu ortaya çıkarıyor çünkü derleyici tamponun paketten daha uzun süre yaşayıp yaşamayacağını garanti edemiyor. Drop uygulamasını kaldırmaya ve bunun yerine tampon serbest bırakılmadan önce çağrılan manuel bir temizleme yöntemi kullanmaya karar veriyorsunuz veya hem ödünç alınmış hem de sahip olunan verileri desteklemek için Cow<'a, [u8]>'ye geçiyorsunuz.

Seçiminiz Cow<'a, [u8]> yaklaşımıdır; bu, ham işaretçileri ve güvensiz Drop mantığını ortadan kaldırmaktadır. Sonuç, sıkı yaşam süresi garantileriyle başarılı bir şekilde derlenen bir ayrıştırıcıdır; bu, hiçbir paketinin temel tamponunun ömrünü aşmadığından emin olurken, ödünç verilmiş durumda performansı korur.

Adayların sıklıkla gözden kaçırdığı noktalar

Derleyici neden PhantomData<&'static T> içeren bir yapı için Drop uygulanmasına izin veriyor, ancak 'a yerel bir yaşam süresi olduğunda PhantomData<&'a T> için bunu reddediyor?

Yaşam süresi 'static olduğunda, referans alınan veri programın tümü boyunca yaşar, bu nedenle yok edilme olasılığı yoktur. 'a yerel bir yaşam süresi olduğunda, veri serbest bırakılabilirken yapı hâlâ var olabilir ve bu durum Drop içinde bir sarkık referansa erişim sağlayabilir. Derleyici yerel yaşam süresi durumunu reddeder çünkü yok edicinin, serbest bırakıldıktan sonra veriye erişmeyeceğini kanıtlayamaz, oysa 'static bu garantiyi doğal olarak sağlar.

PhantomData<T> (sahiplik semantiği) ve PhantomData<&'a T> (ödünç alma semantiği) arasındaki fark nedir ve neden önceki yapının kapsamından çıkmasını engellemez?

PhantomData<T>, yapının bir T'ye sahip olduğu varsayılarak hareket ettiğini belirtir ve bu, varyans ve drop kontrolünü etkiler çünkü yapının bir T'yi düşürebileceğini varsayar, ancak yapının yaşam süresini belirli bir ödünç alınmış yaşam süresi 'a ile bağlamaz. Bu nedenle, derleyici yapının herhangi bir yerel veriden daha uzun süre yaşabileceğini varsayar, dolayısıyla T kendisi yaşam süreleri içermezse. Aksine, PhantomData<&'a T>, yapıyı 'a yaşam süresine kesin olarak kısıtlar, dolayısıyla onun ödünç alma süresini aşmasını engelleyerek yok edicilerde use-after-free'yi önler.

may_dangle niteliğinin (kararsız / kullanımdan kaldırılmış) dropck ile ilgili amacı neydi ve Vec<T> gibi türlere nasıl uygulandı?

#[may_dangle] niteliği, güvenli olmayan kodun derleyiciye, bir türün Drop uygulamasının, bir generic parametre T'nin içeriğine erişmeyeceğini belirtmesini sağlıyordu; bu, T'nin kapsayıcıdan daha uzun sürmediği durumlarda bile geçerliydi. Bu, T değerlerini düşürürken erişmeye ihtiyaç duymadan kendi tamponunu sahiplenen Vec<T> gibi koleksiyonlar için kritik öneme sahipti. Adaylar sıklıkla kaçırır; Drop Check varsayılan olarak ihtiyatlıdır, Drop'un her şeyi erişebileceğini varsayar ve may_dangle bu varsayımdan çıkmayı sağlamak için koleksiyonlar içindeki esneklik için bir mekanizmaydı, bu da güvenli olmayan kod ve sarkık veriye erişimi önlemek için sıkı invariyanlar gerektiriyordu.