RustProgramlamaRust Geliştiricisi

**Pin** kendinden referans veren göstericilerin yapı taşırken geçersiz hale gelmesini nasıl önler?

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

Sorunun cevabı.

Pin kavramı, Rust'ın bellek güvenliğinden ödün vermeden asenkron programlamayı destekleme ihtiyacından doğmuştur. Tarihsel olarak, C++ gibi sistem dilleri kendinden referans veren yapılar kullanmaya izin vermekteydi ancak bellek içinde nesneler taşındığında 'use-after-move' hataları ile karşılaşmaktaydılar. Temel sorun, bir yapının kendi alanlarına işaret eden göstericileri içermesidir; eğer yapı bit düzeyinde yeni bir adrese kopyalanırsa, o içsel göstericiler, serbest bırakılan yığın alanlarına gelen geçersiz referanslar haline gelir. Pin, işaretçi türlerini (Box, Rc, referanslar) sarmalayarak ve alttaki değerin bir daha asla yerinden hareket etmeyeceğini garanti ederek bunu çözer, sadece tip Unpin uyguluyor ise yeniden taşınmanın güvenli olduğunu belirtir. Bu, kendinden referans veren yapılar'ın kararlı adreslere güvenebileceği bir sözleşme oluşturur ve async/await durum makinelerinin askıya alma noktalarından referansları tutmasına olanak tanır.

Hayattan bir durum

Async Rust hizmetimizde, saniyede milyonlarca paket işleyen sıfır kopya ağ protokolü ayrıştırıcısı uygulamamız gerekiyordu. Parser yapısı bir Vec<u8> tamponu ve o tamponu referans alan ayrıştırılmış bir Header yapısı barındırıyordu. Async fonksiyonu await noktasında kontrolü teslim ettiğinde, yürütücü, geleceği işçi thread'leri arasında taşımak için serbest kalıyordu, bu da dilim göstericilerinin geçersiz hale gelmesine ve devam ederken anında tanımsız davranışa neden oluyordu.

Bir yaklaşım, dilim referansları yerine byte indeksleri kullanmayı düşünmekti; tampon içindeki usize kaydırmaları tutmak yerine &[u8] referansları saklamak. Bu yaklaşım, bütünlüğü sağlarken Pin karmaşıklığı olmaksızın tamamen güvenlik sağladı çünkü tamsayılar basitçe kopyalanabilir ve yeniden taşınabilir. Ancak, sürekli sınır kontrolü ve gösterici aritmetiği nedeniyle önemli bir çalışma zamanı yükü getirdi ve sıkı ayrıştırma döngüsü performansımızı yaklaşık on beş yüzde azalttı.

Bir diğer alternatif, tamponu ayrı bir şekilde bellek alanından ayırarak Box::pin kullanmak ve ayrıştırıcı içerisinde ham göstericiler (*const u8) saklamaktı. Bu, gösterici geçersizliğini önlerken, gösterici yere koyma için güvensiz kod blokları ekledi. Ayrıca, manuel bellek yönetimini gerektirdiği için hata alanını artırdı ve Rust derleyicisinin yaşam süremizi doğrulamasını engelledi.

Pin yaklaşımını seçtik, bütün Parser geleceğini pin_project_lite kullanarak sabitlemek için pinledik. Bu çözüm, yığın tahsisi olmadan sıfır maliyetle dilim referanslarını korudu ve yapının async yürütme sırasında hareket etmez kalmasını sağladı. Şimdi hizmetimiz, await sınırları arasında doğrudan bellek referanslarıyla paket işleyebiliyor, çökme veya gösterici kovalamadan kaynaklanan ölçülebilir bir yavaşlama olmadan.