Rust, trait nesneleri (dyn Trait) aracılığıyla çok biçimliliği sağlar; bu nesneler, metod çağrılarını çalışma zamanında yönlendirmek için vtables kullanır. Bu vtable'lar, her uygulama için oluşturulur ve yalnızca trait'in metodlarına karşılık gelen işlev işaretçilerini içerir, böylece farklı somut türler arasında tutarlı bir çağrı anlaşması sağlar.
Bir trait tanımındaki ilişkili sabitlerin (const NAME: Type;) dahil edilmesi, nesne güvenliğini ihlal eder çünkü sabitler monomorfizasyon sırasında çözümlenen derleme zamanı yapılarıdır. Metodların aksine, sabitler vtable'larda yer kaplamaz, çünkü bunların tek tip bir çalışma zamanı temsili yoktur ve genellikle inline ya da salt okunur veri bölümlerinde saklanır. Böyle bir trait için bir dyn Trait oluşturmayı denemek, trait nesnesinin sabit değeri dinamik olarak taşımasını veya referans almasını gerektirir ki bu da vtable'ların ve tür gizlemenin mimari tasarımına aykırıdır.
Bunu çözmek için sabitin bir metoda (fn name(&self) -> Type) veya bir ilişkilendirilmiş türe dönüştürülmesi gerekir; eğer değer bir türü temsil ediyorsa. Bu değişiklik, değer alımını vtable'daki bir işlev işaretçisinin arkasına yerleştirir, böylece nesne güvenliği restoredudulur ve çalışma zamanında minimal bir yük getirilir.
Gömülü bir RTOS için bir donanım soyutlama katmanı tasarlarken, farklı sensör sürücülerini yöneten tek bir Kayıt oluşturulması gerekti. Her sürücünün I2C veri yolu adreslemesi için benzersiz bir const DEVICE_ID: u16 değerine ihtiyacı vardı; bunu başlangıçta trait içinde ilişkili bir sabit olarak tanımlamıştık.
Heterojen sensörleri Vec<Box<dyn Sensor>> içine saklamayı denediğimizde, derleyici nesne güvenliği kurallarını ihlal ettiği için bir hata verdi. Bu, kaydın sensörleri genel olarak sorgulaması için gerekli olan dinamik yönlendirmeyi engelledi.
Üç yaklaşımı değerlendirdik. İlk olarak, DEVICE_ID değerini bir metoda fn device_id(&self) -> u16 dönüştürmek, Vec'in düzgün çalışmasını sağladı ama vtable aramasında bir ceza gerektirdi ve derleme zamanında adres doğrulamasını engelledi. İkincisi, T: Sensor ilişkili genel bir kayıt Vec<Box<T>> kullanmak reddedildi çünkü bu, homojen depolama gerektiriyordu ve sıcaklık ve basınç sensörlerini karıştırma yeteneğini ortadan kaldırıyordu. Üçüncü olarak, sabitleri koruyan ama her yeni sürücü için enum'u değiştirmeyi gerektiren manuel bir tür silme enum'u enum DynSensor { Temp(TempSensor), Press(PressSensor) } uygulamak zorunda kaldık; bu da açık/kapalı ilkesini ihlal etti.
İlk çözümü benimsedik ve elde edilen esneklik için çalışma zamanı maliyetini kabul ettik. Ortaya çıkan sistem, tek bir arayüz aracılığıyla otuz farklı sensör tipini başarılı bir şekilde yönetti, ancak gelecekteki sürücü yazarları için paketin kılavuzlarında mimari değişimi belgeledik.
İlişkilendirilmiş türlerin neden trait nesnelerinde kullanılabildiği ancak ilişkili sabitlerin neden kullanılamadığı, her ikisi de uygulama başına derleme zamanında çözümlenmişken?
İlişkilendirilmiş türler, tür sistemi kimliğine entegre edilmiştir. Box<dyn Trait<AssocType = u32>> gibi bir trait nesnesi oluştururken, ilişkili tür derleyici tarafından oluşturma noktasında bilinen statik tür imzasının bir parçası haline gelir. vtable geçerli kalır çünkü somut tür (ve dolayısıyla ilişkili tür) sabittir. Aksine, ilişkili sabitler değerlerdir, türler değildir. Rust, dyn Trait<CONST = 5> için bir sözdizimi sunmadığından, vtable'lar rastgele veri değerlerini depolayamaz—yalnızca işlev işaretçilerini saklayabilir—ve bu nedenle sabitler silinmiş tür aracılığıyla erişilemez hale gelir.
Trait üzerindeki const generics, sabitleri türün bir parçası haline getirerek ilişkili sabitlerin trait nesneleri ile çalışmasını sağlar mı?
Const generics uygulamak (örneğin, trait Trait<const N: usize>) sabitin türün bir parçası haline gelmesini sağlardı, ancak bu, heterojen koleksiyonları engeller. Her farklı sabit değeri, farklı bir trait türünü başlatır; bu da Box<dyn Trait<1>> ve Box<dyn Trait<2>>'nin farklı Vec kapsayıcılarında depolanan uyumsuz türler olduğu anlamına gelir. Bu yaklaşım, trait nesnelerinin kullanımını motive eden çok biçimlilikli kapsayıcı yeteneğini feda eder ki bu, karışık uygulamaları gerektiren kayıtlar için uygunsuz hale getirir.
İlişkilendirilmiş sabitlerin yokluğu, metadata'ya bağımlı fabrika kayıtları veya eklenti sistemleri gibi kalıpları nasıl etkiler?
Geliştiriciler sıklıkla Vec<Box<dyn Plugin>> üzerinde yineleme yaparak ilişkili bir const VERSION: &str ile filtreleme yapmayı dener, ama metadata'nın silindiğini keşfederler. Çözüm, ya trait nesnesinin yanında bir sarmalayıcı yapı içinde metadata içermektir (örneğin, struct PluginEntry { meta: Metadata, plugin: Box<dyn Plugin> }) ya da TypeId ve Any ile aşağı casting yaparak somut türü geri alıp sabitlere erişmektir. İkincisi, 'static kısıtlamaları gerektirir ve trait nesnesinin soyutlama avantajlarını ortadan kaldırır, bu da trait nesnelerinin kasıtlı olarak derleme zamanı bilgilerini çalışma zamanı dinamikliği için feda ettiğini vurgular.