RustProgramlamaRust Geliştirici

**#[repr(transparent)]** ile **ABI** uyumlu yeni tip sargılarının sunduğu mimari garantiyi açıklayın ve **FFI** bağlamlarında iç türün kesin bellek düzenini bekleyen **repr(Rust)** yapıların yanlış kullanımı durumunda meydana gelen belirsiz davranışı belirtin.

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

Sorunun Cevabı

Sorunun Geçmişi:

RFC 1758'den önce, Rust için FFI'de sıfır maliyetli yeni tipler için bir mekanizma yoktu. Geliştiriciler, belirleyici düzen getiren ancak gereksiz dolgu ekleyebilen #[repr(C)] veya alan sıralaması ve boşluk istismarı gibi agresif derleyici optimizasyonlarına izin veren #[repr(Rust)] kullanımına dayanıyordu. Bu, sargı yapıları aracılığıyla tür güvenliğinin sağlanması ile yabancı işlev çağırmaları için ABI istikrarının garantilenmesi arasında temel bir ikilem oluşturuyordu. #[repr(transparent)] bu gerginliği çözmek üzere tanıtıldı; çünkü tam olarak bir tane sıfır olmayan alan içeren bir yapının, o alt tür ile özdeş bellek düzenine, hizalanmaya ve çağırma konvansiyonuna sahip olacağına söz veriyordu.

Sorun:

Bir #[repr(Rust)] yeni tipi, ham iç türü (örn. u32 handle) bekleyen bir yabancı işlevine referans veya değer olarak geçirildiğinde, derleyici sargının alanlarını yeniden sıralamak veya alan optimizasyonları uygulamak için serbest kalır. #[repr(Rust)] istikrar garantisi sunmadığından, sargı iç tür ile farklı bir boyut, bit deseni geçerliliği veya dolgu alabilir. Bu, yabancı C kodunun yanlış hizalanmış belleği okumasına, geçersiz bit desenlerini geçerli işaretçiler olarak yorumlamasına veya saçmalık verilerine erişmesine neden olur; bu da belirsiz bir davranışa ve sınırda felaket bellek bozulmasına yol açar.

Çözüm:

#[repr(transparent)] derleyiciye, sargının ve tek bir sıfır olmayan alanın, aynı boyut, hizalama ve ABI paylaşmasını sağlamak için zorlar; bu da sargıyı yalnızca derleme zamanında bir soyutlama haline getirir. Derleyici, kesin olarak bir alanın sıfırdan büyük boyutuna sahip olduğunu (ek PhantomData veya birim tür alanlarına izin verir) statik olarak doğrular. Bu, sargının iç türe güvenli bir şekilde dönüştürülebileceği veya doğrudan FFI sınırları boyunca geçebileceği anlamına gelir; bu aşağıda gösterilmiştir:

#[repr(transparent)] pub struct SocketFd(i32); extern "C" { fn close_socket(fd: i32); } pub fn close(sock: SocketFd) { // Güvenli: SocketFd, i32 ile aynı ABI'ye sahiptir unsafe { close_socket(sock.0); } }

Gerçek Hayattan Bir Durum

Bir geliştirici, bir Rust uygulamasını bir Linux çekirdek netlink soket API'si ile entegre eder; bu soket ham tamsayı dosya tanımlayıcıları aracılığıyla iletişim kurar. Soket türlerinin yanlışlıkla karıştırılmasını önlemek için, struct NetlinkSocket(i32)'yi bir yeni tip olarak tanımlar. Başlangıçta #[repr(Rust)] ile işaretlenmiş, referanslarını i32'ye işaretçi bekleyen bir extern "C" geri çağırmaya geçirir. Yerel geliştirme sırasında, bu doğru çalışıyormuş gibi görünür, ancak LTO (Bağlama Zamanı Optimizasyonu) kullanan çıkış derlemelerinde, derleyici NetlinkSocket için agresif boşluk optimizasyonları uygular, bu da bellek temsilini temelde değiştirir. C çekirdek modülü sonuçta bozulmuş bir işaretçi değeri alır ve kritik bir çekirdek paniğine yol açar.

Üç farklı çözüm değerlendirildi. İlk olarak, #[repr(C)] kullanarak kararlı, belirleyici bir düzen sağlama düşünülmüştü. Bu, bellek güvenliğini sağlarken, faydalı boşluk optimizasyonlarını devre dışı bırakıyordu ve potansiyel olarak dolgu baytları ekleyerek yapı boyutunu gereksiz yere şişiriyor ve sadece Rust iç kullanım için API yüzeyini karmaşık hale getiriyordu.

İkincisi, iç alanı (socket.0) her FFI çağrı noktasında manuel olarak dereferanslama denemesi yapıldı. Bu yaklaşım, düzen varsayımlarını önleme konusunda başarılıydı ancak hataya açık ve ayrıntılıydı; bu da soyutlama bariyerini etkili bir şekilde kırıyor ve ham, türsüz tamsayıların kod tabanında kontrolsüz bir şekilde yayılmasına izin veriyordu.

Üçüncü olarak, #[repr(transparent)] NetlinkSocket üzerine uygulandı. Bu garanti, ABI eşdeğerliğini korurken Rust içinde tür ayrımını sağlamış, yapının doğrudan C'ye geçmesine ve manuel açma veya dönüştürme mantığı gerektirmeden geçmesine olanak tanımıştır.

Mühendislik ekibi sonunda #[repr(transparent)]'i benimsedi ve bu, çekirdek paniğilerini tamamen ortadan kaldırırken sıfır maliyetli bir soyutlama sağladı. Sargı artık, Rust içindeki katı bir derleme zamanı koruyucusu görevini görmekte ve tamamen görünmez ve C ABI ile uyumlu kalmaktadır.

Adayların Genellikle Atladığı Noktalar

Neden #[repr(transparent)] tek bir sıfır olmayan alanın sıfır boyutlu bir tür olmasını açıkça yasaklar ve bu kısıtlama, değere göre geçişte FFI'deki belirsiz davranışı nasıl önler?**

#[repr(transparent)] sargının iç türü ile ABI bakımından özdeş olduğunu garanti eder. Bir Sıfır Boyutlu Tür (ZST) sıfır boyuta ve 1 hizalamaya sahiptir. Eğer sargıya yalnızca ZST sarmasına izin verilseydi, ortaya çıkan yapı da sıfır boyutlu olurdu; ancak C sıfır boyutlu türler içermez ve çağrısal konvansiyonları tipik olarak "değerle geçirme" semantiklerine yönelik en az bir bayt veri bekler. Bir ZST değerini FFI üzerinden geçirme, belirsiz bir davranıştır çünkü C sıfır boyutlu değerleri temsil edemez veya düzgün bir şekilde işleyemez. Bu kısıtlama, sargının her zaman alt alanı ile aynı sıfır olmayan boyut ve hizalamayı korumasını sağlar ve C'nin beklentileriyle uyumlu olarak iyi tanımlanmış bir ABI sürdürülebilir.

#[repr(transparent)] enumlar üzerine uygulanabilir mi ve ayrımcılığın FFI sınırlarında görünürlüğünü yöneten kısıtlamalar nelerdir?**

Evet, #[repr(transparent)] yalnızca bir varyanta sahip olan ve bu varyantın da yalnızca bir sıfır olmayan alana sahip olması gereken enumlara uygulanabilir. Enum ayrıca ayrımcı türünü tanımlamak için açık bir ilkel temsil belirtmelidir (örn. #[repr(u8)]). Ancak, #[repr(transparent)] son düzenin sıfır olmayan alan ile özdeş olduğunu garanti eder; bu, ayrımcıyı ABI'den çıkartmaktadır. Sonuç olarak, böyle bir enumun alt alan türü olarak C'ye geçirilmesi güvenlidir, ancak C'den bir ayrımcı değeri erişmeye veya yorumlamaya çalışmak belirsiz bir davranışın sonucudur. Adaylar genellikle ayrımcının düzen içerisinden fiziksel olarak yok olduğunu, sadece gizli veya erişilemez olduğunu yanlış anlarlar.

#[repr(transparent)] yapı içinde ek bir alan olarak PhantomData<T> bulunması, varyans ve düşürme kontrolünü nasıl etkilerken ABI'yi etkilemez?**

PhantomData<T>'nın #[repr(transparent)] yapılar içinde ikincil bir alan olarak açıkça izin verilmiştir; çünkü sıfır boyutludur ve 1 hizalamaya sahiptir. Boyutu, hizalaması veya sargının ABI'sini değiştirmediği için (çünkü #[repr(transparent)] yalnızca tek bir sıfır olmayan alanı dikkate alır) bu, derleyiciye tür parametresi T ile olan yapısal ilişkiyi bildirir. Bu varyansı etkiler: örneğin, bir Wrapper<T>(*const T, PhantomData<fn(T)>) yapısı, PhantomData işareti nedeniyle T üzerinde ters varyans olacaktır. Ayrıca, yapıların T türünde veri sahip olabileceğini kavramsal olarak tanımasına olanak tanır ve 'statik ömürler taşıyan T olduğunda güvensizliği önleyerek Drop Check (dropck) analizini etkinleştirir. Adaylar, genellikle PhantomData'nın bellek düzenini etkilediğini veya generic FFI sargıları için ömür ve sahiplik tutarlılıklarını korumada önemli rolünü göz ardı ederler.