Prelude’e Eklenenler
Özet
TryInto,TryFromveFromIteratortrait’leri artık prelude’un bir parçasıdır.- Bu durum, trait metod çağrılarını belirsiz hale getirerek bazı kodların derlenememesine yol açabilir.
Ayrıntılar
Standart kütüphanenin prelude’u,
her modülde otomatik olarak içe aktarılan her şeyi barındıran modüldür.
Option, Vec, drop ve Clone gibi sık kullanılan öğeleri içerir.
Rust derleyicisi, prelude’a yapılan eklemelerin mevcut kodu bozmamasını sağlamak
için elle içe aktarılan öğelere prelude’dan gelenlerden daha yüksek öncelik verir.
Örneğin ornek adlı bir crate ya da modülünüz varsa ve içinde pub struct Option;
tanımlanmışsa, use ornek::*; ifadesi Option adını tartışmasız biçimde
ornek içindeki tipe bağlar; standart kütüphanedekine değil.
Ancak prelude’a bir trait eklemek, mevcut kodu daha ince bir şekilde bozabilir.
Örneğin BenimTryInto trait’inden gelen x.try_into() çağrısı, std içindeki
TryInto da içe aktarıldığında derlenmeyebilir. Çünkü bu kez try_into
çağrısının hangi trait’ten geldiği belirsizleşir. TryIntoyu şimdiye kadar
prelude’a eklememiş olmamızın nedeni de buydu; aksi halde çok sayıda kod kırılırdı.
Çözüm olarak Rust 2021 yeni bir prelude kullanır. Bu yeni sürüm, üç ek dışında mevcut prelude ile aynıdır:
Takip konusu burada bulunabilir.
Taşıma
2021 sürümünün bir parçası olarak, Rust 2018 kod tabanlarının Rust 2021’e
otomatik taşınmasına yardımcı olmak için rust_2021_prelude_collisions
taşıma lint’i eklendi.
Kodunuzu Rust 2021 ile uyumlu hale getirmek için şunu çalıştırın:
cargo fix --edition
Bu lint, yeni prelude trait’lerinden birindeki metodlarla aynı ada sahip fonksiyon ya da metod çağrılarını tespit eder. Bazı durumlarda, önceden çağırdığınız aynı işlevin çağrılmaya devam etmesini sağlamak için çağrıları yeniden yazabilir.
Kodunuzu elle taşımak ya da cargo fixin ne yaptığını daha iyi anlamak
istiyorsanız, aşağıda taşımanın gerekli olduğu ve olmadığı durumları özetledik.
Taşıma gerekli
Çakışan trait metodları
Kapsamda bulunan iki trait aynı metod adına sahipse, hangi metodun kullanılacağı belirsiz olur. Örneğin:
trait BenimTrait<A> {
// Bu ad, `std` içindeki `FromIterator` trait'inin `from_iter` metoduyla aynıdır.
fn from_iter(x: Option<A>);
}
impl<T> BenimTrait<()> for Vec<T> {
fn from_iter(_: Option<()>) {}
}
fn main() {
// Vec<T>, hem `std::iter::FromIterator` hem de `BenimTrait` uygular
// Eğer iki trait de kapsamdaysa (Rust 2021'de olduğu gibi),
// hangi `from_iter` metodunun çağrılacağı belirsizleşir
<Vec<i32>>::from_iter(None);
}
Bunu tam nitelikli sözdizimiyle düzeltebiliriz:
fn main() {
// Artık hangi trait metoduna başvurduğumuz açık
<Vec<i32> as BenimTrait<()>>::from_iter(None);
}
dyn Trait nesneleri üzerindeki doğal metodlar
Bazı kullanıcılar, metod adı yeni bir prelude trait’i ile çakışan bir
dyn Trait değeri üzerinde metod çağırır:
#![allow(unused)]
fn main() {
mod alt_modul {
pub trait BenimTrait {
// Bu, `TryInto::try_into` ile aynı ada sahiptir
fn try_into(&self) -> Result<u32, ()>;
}
}
// `BenimTrait` burada kapsamda değildir ve yalnızca `alt_modul::BenimTrait` yoluyla anılabilir
fn islem(f: Box<dyn alt_modul::BenimTrait>) {
// Eğer `std::convert::TryInto` kapsamdaysa (Rust 2021'de olduğu gibi),
// hangi `try_into` metodunun çağrılacağı belirsizleşir
f.try_into();
}
}
Statik gönderim metodlarının aksine, bir trait nesnesi üzerinde trait metod
çağırmak için trait’in kapsamda olması gerekmez. Yukarıdaki kod, çakışan adlı
başka bir trait kapsamda olmadığı sürece çalışır. TryInto trait’i kapsamda
olduğunda ise belirsizlik doğar: Çağrı BenimTrait::try_into mu olmalı,
yoksa std::convert::TryInto::try_into mu?
Bu gibi durumlarda ek dereference ekleyerek veya metod alıcısının türünü daha
açık hale getirerek sorunu çözebiliriz. Böylece prelude trait’indeki metodlar
yerine dyn Trait üzerindeki metod seçilir. Örneğin yukarıdaki f.try_into()
çağrısını (&*f).try_into() biçimine çevirmek, çağrının yalnızca
BenimTrait::try_into metoduna gideceğini garantiler.
Taşıma gerekmiyor
Doğal metodlar
Birçok tür, trait metodlarıyla aynı ada sahip kendi doğal metodlarını tanımlar.
Örneğin aşağıda BenimYapi yapısı, standart kütüphanedeki FromIterator
trait’inin metoduyla aynı ada sahip from_iter metodunu uygular:
#![allow(unused)]
fn main() {
use std::iter::IntoIterator;
struct BenimYapi {
data: Vec<u32>
}
impl BenimYapi {
// Bu, `std::iter::FromIterator::from_iter` ile aynı ada sahiptir
fn from_iter(iter: impl IntoIterator<Item = u32>) -> Self {
Self {
data: iter.into_iter().collect()
}
}
}
impl std::iter::FromIterator<u32> for BenimYapi {
fn from_iter<I: IntoIterator<Item = u32>>(iter: I) -> Self {
Self {
data: iter.into_iter().collect()
}
}
}
}
Doğal metodlar her zaman trait metodlarından önce gelir; bu yüzden burada bir taşıma gerekmez.
Uygulama Notu
Lint, 2021 sürümünü bir kod tabanına getirmenin ad çözümleme çakışmasına yol açıp açmayacağını belirlerken birkaç etkeni hesaba katmak zorundadır. Bu etkenler şunlardır:
- Çağrı bir tam nitelikli çağrı mı, yoksa nokta ile metod çağrısı sözdizimi
mi kullanıyor?
Nokta ile metod çağrısı sözdiziminde otomatik referans ve otomatik
dereference devreye girdiği için bu durum ad çözümlemesini etkiler. Elle
referans alma ya da dereference yapma, nokta sözdiziminde önceliği açıkça
belirlemeye yardım eder. Tam nitelikli çağrıda ise metod yolunda tür ve
trait adı açıkça yazılmalıdır; örneğin
<Tur as Trait>::metod. - Bu bir doğal metod mu, yoksa trait metodu mu?
selfalan doğal metodlar, trait metodlarından önce geldiği içinTryInto::try_intoçağrısına göre öncelik kazanır. Ancak&selfya da&mut selfalan doğal metodlar, otomatik referans gerektirdikleri için aynı önceliği kazanmaz;TryInto::try_intoiseselfaldığı için buna ihtiyaç duymaz. - Bu metodun kaynağı
coreya dastdmi? Trait’ler kendi kendileriyle çakışamayacağı için bu önemlidir. - Verilen tür, çakışma yaşayabileceği trait’i gerçekten uyguluyor mu?
- Metod dinamik gönderim üzerinden mi çağrılıyor?
Yani
selftürüdyn Traitmi? Böyleyse, trait içe aktarmaları çözümlemeyi etkilemez ve taşıma lint’inin devreye girmesi gerekmez.