ManuallyDrop, bir değerin kapsam dışına çıkarken Drop::drop'ın otomatik çağrısını bastırır. Dizi veya benzeri sabit boyutlu koleksiyonlar için IntoIterator'ı uygularken, elemanlar ptr::read aracılığıyla çıkarılır; bu durum, kaynağın belleğini mantıksal olarak başlatılmamış bırakacak şekilde bit düzeyinde bir taşıma gerçekleştirir. ManuallyDrop olmadan, bir öğenin yok edilmesi sırasında bir panik oluşursa, geri dönüş mekanizması dizinin yok edicisini çağırarak, zaten taşınmış olan tüm alanlar da dahil olmak üzere tüm yuvaları bırakmayı dener; bu durum, çift bırakmalarla tanımsız bir davranışa yol açar. Belleğin ManuallyDrop içinde sarılmasıyla birlikte, uygulayıcı yalnızca geriye kalan öğeleri bırakmaktan sorumlu olur, bunun genellikle bir indeksin izlenmesi ve özel bir Drop uygulaması içinde suffix'in manuel olarak bırakılmasıyla gerçekleştirilir.
Bir FixedVec<T, const N: usize> oluşturuyorsunuz—sabit kapasiteye sahip, yığın tahsis edilen bir vektör—ve koleksiyonu değerle tüketen IntoIterator'ı uygulamanız gerekiyor.
Ana problem, öğe çıkarımı sırasında ortaya çıkar: dahili diziden her bir T'yi hareket ettirip döndürmeniz gerekir. Eğer kullanıcının T uygulaması yok edilme sırasında bir panik yaşarsa ve yineleyici kısmen tüketilmişse, geri dönüş süreci hala geri kalan öğeleri temizlemelidir. Ancak, bazı öğeler zaten ptr::read aracılığıyla bit düzeyinde taşınmış ve orijinal bellek konumları başlatılmamış kalmıştır. Eğer destekleyici dizi ManuallyDrop içinde sarılmamışsa, yok edici tüm yuvaları canlı T örnekleri olarak ele alacak ve onlarda drop_in_place'i çağırarak, hareket ettirilen öğeler için çift bırakmalara (tanımsız davranış) ve potansiyel kullanımdan sonra serbest bırakmaya yol açacaktır.
Çözüm 1: Tüm yuvalar için Option<T> kullanın. Bu yaklaşım, dizide Option<T> saklayarak değerleri take() almanıza ve arada None bırakmanıza olanak tanır. Artıları: Tamamen güvenli, gerekli unsafe kod blokları yok, net bir anlam. Eksileri: Ayrım için bellek aşırı yükü (genellikle kelime boyutuna kadar yastıklanmış 1 byte her öğe için), önbellek verimsizliği ve kullanılmayacak olmasına rağmen tüm yuvaları Some(value) ile başlatma gerekliliği.
Çözüm 2: Dizi için ManuallyDrop kullanın. Dahili [T; N]'yi ManuallyDrop<[T; N]> içinde sarın. Değer verirken, değeri okuyun ve bir sayacı artırın. Yineleyicinin Drop'unda, sadece kalan aralığı ptr::drop_in_place ile manuel olarak bırakın. Artıları: Sıfır yük, ham T ile aynı bellek düzeni, doğrudan bellek manipülasyonuna izin verir. Eksileri: unsafe kod gerektirir, hangi yuvaların başlatıldığını izleme konusunda karmaşık bir invariant bakımı, manuel bırakma mantığı hatalı ise sızıntı riski.
Çözüm 3: Bit düzeyinde geçerlilik maskesi kullanın. Hangilerinin canlı olduğunu izlemek için ayrı bir bitset tutun. Artıları: Bitset için güvenli soyutlamalar kullanıyorsanız unsafe kod yok. Eksileri: Önemli karmaşıklık, her erişimde bit manipülasyonu aşırı yükü ve önbellek dostu erişim modelleri.
Seçilen Çözüm ve Sonuç: std::array::IntoIter davranışını eşlemek için Çözüm 2 seçilmiştir. Yineleyici yapısı, diziyi ManuallyDrop içinde sarar ve mevcut indeksi izler. next() metodu, öğeleri dışarı taşımak için ptr::read kullanır. Drop uygulaması, indeksi kontrol eder ve kalan dilimde ptr::drop_in_place çağrısı yapar. Bu, önceden bırakılmış bir öğe düşürülürken bir panik oluşursa, geri dönüş işleminin yalnızca dokunulmamış suffix'i bırakmasını sağlayarak hem sızıntıları hem de çift bırakmaları engeller. Sonuç, panik yapan yok edicilerin varlığında bile bellek güvenliği inançlarını koruyan sıfır maliyetli bir soyutlamadır.
ManuallyDrop, Copy trait ile nasıl etkileşime giriyor ve bu, Copy türleri için yineleyiciler uygularken neden ince hatalara neden olabilir?
ManuallyDrop<T>, sadece T: Copy olduğunda Copy uygulayacaktır. ManuallyDrop içinde sarılmış Copy türlerine sahip bir diziyi yineleyip, ptr::read veya basit atama kullanıldığında, hareket yerine bit düzeyinde kopyalar oluşturulur. Adaylar genellikle ManuallyDrop'ın tüm çoğaltma biçimlerini önlediğini varsayar, ancak Copy türlerinde derleyici, onu hareket ettirmek istediğinizde değerin açık bir şekilde kopyalanabileceği durumlar oluşturmak üzere değeri kopyalayabilir; bu da "hareket ettirilmiş" değerin kaynağın konumunda hala canlı kabul edilmesine yol açar. Bu, testlerde tamsayılarla çift bırakma sorunlarını maskeleyebilir, ancak Copy olmayan türlerde tanımsız davranışla ortaya çıkabilir. Doğru yaklaşım, ManuallyDrop içeriklerinin Copy kısıtlamalarına bakılmaksızın taşınmış olarak ele alınması veya açık bir değiştirme takibi sonrası ManuallyDrop::into_inner kullanılmasıdır.
Bir panik gerçekleştiğinde, yineleyici üzerinde sadece mem::forget çağırmanın yetersiz olmasının nedeni nedir, bunun yerine kısmi tüketime yönelik özel bir Drop uygulaması yapmanız gerekir?
mem::forget, yineleyiciyi bırakmadan tüketir, bu da zaten hareket ettirilmiş öğeleri iki kez bırakmaktan kurtulsa da, aynı zamanda henüz verilmemiş tüm kalan öğeleri sızdırır ve Rust koleksiyonlarının beklenen kaynak yönetimi garantilerini ihlal eder. Drop trait'i tam olarak geri dönüş sırasında temizliği sağlamak için vardır; hata yollarında mem::forget'a dayanmak, bir güvenlik sorununu kaynak sızıntısına dönüştürür. Doğru şekilde, ManuallyDrop kullanarak depolamanın otomatik yok edilmesini devre dışı bırakmak ve ardından yalnızca bırakılmamış öğeleri Drop uygulamasında manuel olarak bırakmak, herhangi bir sızıntı ve çift bırakma olmamasını sağlar.
ptr::read kullanarak ManuallyDrop<T> yuvasından çıkış yapmak ile ManuallyDrop::into_inner kullanmak arasında ne fark vardır ve her biri yineleyici uygulamasında ne zaman uygundur?
ptr::read, değerin bit düzeyinde bir kopyasını alır ve kaynak belleği değiştirmeden (hala geçerli bir T tutar) çalışır; oysa ManuallyDrop::into_inner, değeri çıkarmak için ManuallyDrop sarmalayıcısını kendisi tüketir. Yineleyici uygulamasında ptr::read, ManuallyDrop kabuğunun yerinde kalması gerektiği durumlarda kullanılır (örneğin, ManuallyDrop<T> dizisinde); böylece kalan yuvalar yinelemeye ve muhtemel olarak daha sonra bırakmaya imkan tanır. into_inner, ManuallyDrop değerinin tamamını bir seferde tükettiğinizde ve kısmi durum takibi yapmayacağınızda uygundur. Bir dizinin bireysel öğeleri üzerinde into_inner kullanmak, yeniden sarılmayı veya karmaşık gösterge aritmetiğini gerektirirken, ptr::read, diziyi potansiyel olarak başlatılmamış bir veri hamuru olarak ele almanıza olanak tanır.