RustProgramlamaRust Geliştirici

İki ilgisiz crate'in aynı dış trait'i paylaşılan bir dış tür için aynı anda uygulamasını önleyen mekanizma nedir ve crate-eşel türlerin kavramı bu tür genişletmeler için yasal bir yol sunar?

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

Sorunun Cevabı

Rust derleyicisi, her trait-tür çifti için tüm bağımlılık grafiği boyunca en fazla bir uygulamanın bulunmasını garanti etmek amacıyla yabancı kuralı (koherens sisteminin temel bileşeni) ile uygulamaları denetler. Bu kural, uygulama bloğunun geçerli olabilmesi için ya uygulanan trait'in ya da uygulamanın alındığı türün mevcut crate içinde tanımlanması gerektiğini belirtir ki buna "yerel" crate denir. Her iki trait ve türün de yabancı (dış) olduğu uygulamaları yasaklayarak, Rust iki bağımsız crate'in aynı hedef için çelişkili uygulamalar sunması senaryolarını engeller, bu da tanımsız davranış veya aşağıdaki projelerde çözülemeyen belirsizliklere neden olabilir. "Yerel tür" istisnası, geliştiricilerin bir yerel tür için bir dış trait'i (kendi yapılarına standart operatörler eklemek için) veya bir dış tür için bir yerel trait'i (genişletme yöntemleri sağlamak için) uygulamasına izin verir, böylece belirsiz monomorfizasyon ve çalışma zamanı dağıtım tabloları olmadan sıfır maliyetli soyutlama sağlar.

Hayattan Bir Durum

Ekibimiz, şemayı JSON'a seri hale getirmek için serde çerçevesini kullanarak bir yüksek performanslı GraphQL sunucu kütüphanesi geliştiriyordu. Yerel Schema yapımız için serde'nin Serialize trait'ini uygulamak kolaydı çünkü tür yereldi. Ancak, ayrıca graphql_parser crate'inden gelen Document türü için özel formatlamaya ihtiyacımız vardı, böylece bunu standart Display trait'i aracılığıyla günlükleme sistemimize entegre edebildik. Bu tasarım gerginliği yarattı çünkü hem Document hem de Display yabancıydı ve ana crate yukarıda kendi Display uygulamasını eklerse gelecekte bir tutarsızlık olabileceğinden endişelendik.

İlk düşündüğümüz çözüm Newtype deseniydi; graphql_parser::Document'ı bir tuple struct ile struct DocWrapper(graphql_parser::Document) sarmalayarak Display'i DocWrapper üzerinde uygulamak.

Bu yaklaşım yabancı kuralına mükemmel bir şekilde uyar çünkü DocWrapper yerel bir türdür ve Rust yeni türler için sıfır maliyetli soyutlama sağlar, çalışma zamanı yükü olmadan. API üzerinde tam kontrol sağlamamıza ve gelecekteki çatışmaları önlememize olanak verir. Ancak, bu dönüşümler için önemli bir temizlik kâğıdı getirir ve ergonomiyi bozabilir; kullanıcılar örnekleri manuel olarak sarmalamalı ya da sağlanan From uygulamalarına güvenmeli, potansiyel olarak kamu API'sini uygulama detaylarını sızdıran sarmalayıcı türleri ile kalabalıklaştırabilir.

İkinci çözüm, local olarak tanımlanan GraphQLDisplay adlı bir genişletme trait'i oluşturmaktı ve bunu doğrudan yabancı Document türü için uygulamaktı.

Bu, yabancı kuralı çerçevesinde yasaldır çünkü trait kendisi yereldir, tür yabancı olsa bile, sarmalayıcı türlerin ergonomik sürtüşmesini önler ve yöntem zincirleme sözdizimini etkinleştirir. Kritik dezavantaj, bunun Rust'un standart formatlama makroları olan format! veya println! ile entegre edilmemesidir, çünkü özel olarak Display trait'ini gerektirir; kullanıcıların özel bir yöntemi aramaları ve çağırmaları gerekirdi, bu da standart Rust gelenekleriyle tutarsız bir deneyim oluştururdu.

Sonunda, Document türü için Newtype desenini seçtik çünkü uzun vadeli istikrar ve standart kütüphane entegrasyonunu kısa vadeli ergonomik maliyetlerden daha değerli bulduk. DocWrapper kullanarak, hata günlüklememizin standart formatlama araçlarını kullanabilmesini sağladık, özel makrolar veya trait ithalatları gerekmedi. Schema türü için ise sadece Serialize'yi türettik çünkü hem tür hem de türetme makrosu yerel olarak bulunuyordu. Sonuç olarak, tüm trait çözümlemelerinin derleme zamanında belirsiz olmadığı, derlemenin belirsizlik çözümlemesi yükü olmadan hızlı kaldığı ve graphql_parser yukarıda kendi Display uygulamasını eklese bile elmas bağımlılık sorunları riskini ortadan kaldıran tutarlı, geleceğe dönük bir API sağladık.

Adayların Sıklıkla Gözden Kaçırdığı Şeyler

Yabancı kuralı, Vec<T> gibi genel türlere nasıl uzanır ve neden Vec<LocalType> için bir yabancı trait'i uygulamak serbestken Vec<ForeignType> yasaktır?

Yabancı kuralı, "yerel tür kapsamı" kavramı aracılığıyla genel türlere uygulanır; bu, genel yapının içinde en az bir tür parametresinin mevcut crate'e yerel olmasını gerektirir. Bu nedenle, impl ForeignTrait for Vec<LocalType> geçerlidir çünkü LocalType uygulamayı yerel crate'e bağlar, başka bir crate'in o özel somut tür için çelişkili bir uygulama yazmasını sağlar. Tersine, impl ForeignTrait for Vec<ForeignType> kuralı ihlal eder çünkü hem trait hem de tüm tür argümanları dışarıdadır, bu da ForeignType'ı tanımlayan crate'in daha sonra Vec<ForeignType> için aynı trait'i uygulama riski taşır, bu da koherens çatışmalarına yol açar. Adaylar genellikle bu kapsamın karmaşık genel türlere de özsel olarak uygulandığını ancak o konteynerin kendisine yayılmadığını gözden kaçırır.

Neden bir üst crate'teki genel uygulama (örneğin impl<T> Trait for T where T: ToString) alt crate'lerin belirli türler için (yerel olanlar dahil) o trait'i uygulamasını engeller?

Bir genel uygulama, belirli trait sınırlarını karşılayan tüm türler için varsayılan bir davranış sağlar ve Rust'un koherens kuralları, mevcut bir genel uygulama ile örtüşebilecek herhangi bir somut uygulamayı yasaklar. Eğer bir üst crate impl<T> Serialize for T where T: ToString sağlarsa, alt crate'ler ToString uygulayan herhangi bir tür için Serialize'yi uygulayamaz, bu tür yerel olsa bile; çünkü derleyici, genel uygulama ve somut uygulamanın karşılıklı olarak hariç tutulduğunu garanti edemez. Bu, yabancı kuraldan farklıdır; yabancı kural, kimin bir uygulama yazabileceğini denetlerken, örtüşme kuralı, iki geçerli uygulamanın aynı isim alanında bir arada bulunup bulunamayacağını denetler. Adaylar, bu kavramları genellikle karıştırır ve sözdizimsel olarak yabancı kurallar kapsamında geçerli olan somut uygulamaları yazmaya çalışır ancak yukarıdaki genel uygulamalarla örtüşme nedeniyle reddedilirler.

Yabancı kuralıyla ilgili temel trait'lere (örneğin, Fn, FnMut ve FnOnce) neden özel bir muamele yapılır ve bu, kapanışların bu trait'leri uygulamasını koherens ihlali olmadan nasıl izin verir?

Fn ailesi "temel" olarak adlandırıldığından, yabancı kuralı, genel parametrelerinde yerel türler içeren yabancı türler için bu trait'lerin uygulamalarına izin verecek şekilde gevşetilir. Bu "ters" kural, bir uygulamanın geçerli olup olmadığını belirlerken koherens açısından trait'in yerel olarak kabul edilmesini sağlar. Örneğin, crate'inizde tanımlanan bir kapanış, crate'inize yerel olan benzersiz, isimlendirilmemiş bir türdür ve FnOnce için bu kapanış üzerinde uygulama yapmak yasaldır, çünkü FnOnce standart kütüphanede tanımlanmış olsa ve kapanışın türü opak olsa bile. Adaylar genellikle bu mekanizmayı gözden kaçırır çünkü bu, Rust'ın kapanışları nasıl ele aldığına dair bir uygulama detaydır ama anlamak, kapanışların yerel ortamları yakalamasının ve yabancı trait'leri uygulamasının neden yeni tür sarmalayıcılara ihtiyaç duymadan veya koherens hataları tetiklemeden mümkün olduğunu netleştirir.