RustProgramlamaRust Geliştirici

**Rust**'ın hem **Copy** hem de **Drop**'u uygulayan türlerin yasaklanması arkasındaki mimari nedeni açıklayın ve bu kısıtlamanın önlediği belirli bellek güvenliği ihlalini tanımlayın.

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

Sorunun cevabı

Sorunun tarihi

Copy trait'i, Rust'ın erken tasarımında kaynak yönetim endişeleri olmadan basit bit seviyesinde kopyalama ile çoğaltılabilen türler için bir işaretçi olarak ortaya çıktı. Drop, dosya tanıtıcıları veya yığın belleği gibi dış kaynakları yöneten türler için belirleyici kaynak temizliği sağlamak amacıyla tanıtıldı. İkili çoğaltma ile benzersiz sahiplik arasındaki çatışma, tasarımcılar bit seviyesinde kopyaların paylaşılamayan kaynak yönetimlerini paylaşacağını fark ettiklerinde belirgin hale geldi. Sonuç olarak, derleyici, her iki trait'i de aynı anda uygulamaya çalışan herhangi bir türü reddetmek üzere tasarlandı.

Sorun

Eğer bir tür Drop'u uyguluyorsa (örneğin, bir dosya tanıtıcısını yönetiyorsa) ve aynı zamanda Copy'yse, değeri yeni bir değişkene atamak, iki bit seviyesinde özdeş kopya oluşturur. Her iki kopya kapsamdan çıktığında, özel Drop uygulaması aynı temel kaynak üzerinde iki kez çalışır. Bu, double-free açığına veya ilk düşürme ile geçersiz kılınan bir kaynağın ikinci kopya tarafından erişilmesi durumunda use-after-free'ye yol açar, bu da bellek güvenliğini tehlikeye atar.

Çözüm

Rust derleyicisi, trait sisteminde, bir türün hem Copy'yi hem de Drop'u uygulamasını açıkça yasaklayan bir uyumluluk kontrolü içerir. Bu kısıtlama, geliştiricilerin özel bir yok etme işlemi gerektiren türler için Clone (açık çoğaltma) kullanmalarını zorunlu kılarak, uygulamanın referans sayımlarını doğru bir şekilde artırmasına veya derin kopyalama yapmasına olanak tanır. Her mantıksal varlığın ait olduğu belirli bir düşürme ile eşleştiğinden emin olarak, tür sistemi, güvenlik garantilerinden ödün vermeden sıfır maliyetli soyutlamalar sağlamaktadır.

Hayattan bir durum

Bir dış C kütüphanesindeki bir bağlantı nesnesine işaret eden ham bir gösterici sarmalayan bir DatabaseHandle yapısını düşünün. Uygulama, günlükleme için birden fazla closure'a değerleri geçmeye ihtiyaç duyar, ancak her bir tutucu, bırakıldığında benzersiz bağlantısını kapatmalıdır. Eğer tutucu Copy olsaydı, örtük kopyalama, aynı temel C kaynağının mülkiyetini talep eden birden fazla tutucu yaratırdı ki bu da kapsamdan çıkıldığında çift kapamaya veya use-after-free'e yol açardı.

Bir yaklaşım, Copy'ye izin vermek ve iç referans sayımı kullanarak Drop'u uygulamaktı. Ancak bu, her tutucu için senkronizasyon yükü ekleyecek, ikili boyutu artıracak ve tüm işlemler boyunca çalışma zamanıyla ilgili maliyetleri yükseltecekti. Ayrıca, ham işaretçinin Arc'den atomik bir şekilde çıkartılması gerektiği FFI sınırını karmaşıklaştırmakta ve düşürme mantığının kendisinin Rust koduna geri çağrılmasına bağlı potansiyel birbirini bloklayan durumlar getirmekteydi.

Diğer bir yaklaşım, Copy'yi kullanmak ancak kullanıcıların değeri düşürmeden önce manuel olarak bir close yöntemini çağırmaları gerektiğini belgelemektir. Bu, bellek güvenliğinin yükünü tamamen programcıya yükler, Rust'ın derleme zamanında hataları önleme ilkesiyle çelişir. Geliştiricilerin kapatmayı çağırmayı unutmaları durumunda kaynak sızıntısına veya tutucuyu yanlışlıkla kopyalayıp her iki kopyayı da kapatmaya çalışmalarıyla çift kapamaya yol açar.

Seçilen çözüm, Copy'yi kaldırmak ve Clone'u manuel olarak uygulamak, ayrıca Drop'u üzerinde çalışmaktı. Clone, yeni bir veritabanı bağlantısı açarak derin bir kopyalama gerçekleştirir ve her örneğin kendi belirli kaynağını sahiplenmesini sağlar, temel C işaretçisinin ortak kullanılmasını önler. Drop yalnızca kendi bağlantısını kapatır, derleyici yanlışlıkla bit seviyesinde kopyaları önler, çalıştırma zamanı yükü olmadan güvenliği korur.

Tür sistemi şimdi, yanlış kopyalamayı derleme zamanında önlüyor, geliştiricileri açık bir şekilde clone çağırmaya zorlayarak kaynak edinimini kaynak kodunda görünür hale getiriyor. Program, tutucular iş parçacıklarına veya closure'lara geçirildiğinde çift serbest bırakma hatalarından kaçınır ve belirleyici yıkım garantileri, atomik işlemler veya manuel bellek yönetimi gerektirmeden korunur.

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

Bir Vec içeren bir yapı için neden Copy türeteyim?

Bir Vec, yığındaki bellekten hafıza sahibi olduğu ve kapsamdan çıktığında bu bellek alanını serbest bırakmak için Drop'u uyguladığı için, bir Vec içeren bir yapının Copy olması, yığın üzerinde aynı yığın tamponuna sahip iki yapı oluşturur ve her ikisi de yığına işaret eden aynı işaretçiye sahiptir. İlk yapı düşürüldüğünde bellek serbest bırakılır; ikinci yapı düşürüldüğünde, aynı belleği tekrar serbest bırakmaya çalışır ve tanımsız bir davranışa neden olur. Rust, Copy türünün tüm alanlarının da Copy olmasını gerektirerek, iç içe geçmiş Drop uygulamalarının bulunmadığını sağlamaktadır.

mem::forget, Copy ve Drop konusundaki sorunları önler mi?

std::mem::forget, bir değer tüketerek yıkıcısının çalışmasını engeller, ancak bu yalnızca tek bir sahip olan değeri etkiler, onun tüm kopyalarını değil. Copy ve Drop'a izin verilseydi, bir kopyayı unutmak, diğer bit seviyesindeki kopyaların kapsamdan çıktığında Drop uygulamalarını çalıştırmasını engellemezdi. Kalan düşmeler hâlâ aynı temel kaynağı serbest bırakmaya çalışır, bu da use-after-free veya double-free'ye yol açar, unutulmuş örneğe bakılmaksızın.

ManuallyDrop kullanarak Copy'yi güvenli bir şekilde uygulayabilir miyim?

Bir alanı ManuallyDrop ile sarmalamak, Drop'un otomatik olarak çağrılmasını engeller, bu da teknik olarak dış yapının Copy'yi türetmesine izin verir. Ancak bu, her bir kopya için ManuallyDrop::drop'u çağırma sorumluluğunu kullanıcıya kaydırır, bu da etkili bir manuel bellek yönetimi senaryosu yaratır. Kullanıcı, bir kopyayı bile düşürmeyi unutursa, kaynak kalıcı olarak sızar; Rust bu iterasyonlar için kaynak sahibi türleri için bu deseni yasaklar çünkü bu, belirleyici, otomatik temizleme güvenliğini zedelemektedir.