Never Türü Geri Dönüş Değişikliği
Özet
- Never türünden (
!) herhangi bir türe (“never-to-any”) yapılan zorlama artık birim türüne (()) değil, yine never türüne (!) geri döner. never_type_fallback_flowing_into_unsafelint’i artık varsayılan olarakdenydir.
Ayrıntılar
Derleyici, bir [zorlama noktasında][] ! (never) türünde bir değer gördüğünde,
tür denetleyicisinin herhangi bir türü çıkarabilmesine izin vermek için örtük
bir zorlama ekler:
#![allow(unused)]
fn main() {
#![feature(never_type)]
// Bu:
let x: u8 = panic!();
// ...derleyici tarafından özünde şuna dönüştürülür:
let x: u8 = absurd(panic!());
// ...buradaki `absurd` aşağıdaki fonksiyondur
// (`!` her zaman ulaşılamaz kodu işaretlediği için bu güvenlidir):
fn absurd<T>(x: !) -> T { x }
}
Tür çıkarılamıyorsa bu durum derleme hatalarına yol açabilir:
#![allow(unused)]
fn main() {
#![feature(never_type)]
fn absurd<T>(x: !) -> T { x }
// Bu:
{ panic!() };
// ...şuna dönüştürülür:
{ absurd(panic!()) }; //~ HATA `absurd` icin tur cikarilamiyor
}
Bu tür hataları önlemek için derleyici, absurd çağrılarını nereye eklediğini
hatırlar ve türü çıkaramazsa bunun yerine geri dönüş türünü kullanır:
#![allow(unused)]
fn main() {
#![feature(never_type)]
fn absurd<T>(x: !) -> T { x }
type Fallback = /* Keyfi seçilmiş bir tür! */ !;
{ absurd::<Fallback>(panic!()) }
}
Buna “never türü geri dönüşü” denir.
Tarihsel olarak geri dönüş türü () idi. Bu, geri dönüş mekanizması olmasa
derleyicinin () çıkaramayacağı durumlarda bile ! türünün kendiliğinden ()
türüne zorlanmasına neden oluyordu. Bu kafa karıştırıcıydı ve ! türünün
kararlı hale gelmesini engelliyordu.
2024 sürümünde geri dönüş türü artık ! oldu. Bu değişikliği daha sonra tüm
sürümlere yaymayı planlıyoruz. Böylece davranış daha sezgisel hale gelir.
Artık ! verdiğinizde ve onu başka bir şeye zorlamak için neden yoksa, değer
! olarak kalır.
Bazı durumlarda kodunuz geri dönüş türünün () olmasına dayanıyor olabilir;
bu yüzden bu değişiklik derleme hatalarına ya da davranış değişikliklerine yol açabilir.
never_type_fallback_flowing_into_unsafe
never_type_fallback_flowing_into_unsafe lint’inin varsayılan düzeyi 2024
sürümünde warndan denyye yükseltildi. Bu lint, ! geri dönüşü ile unsafe
kod arasındaki ve tanımsız davranışa yol açabilecek belirli bir etkileşimi
tespit etmeye yardım eder. Tam açıklama için bağlantıya bakın.
Taşıma
Otomatik bir düzeltme yoktur; ancak sürüm değişikliğiyle bozulacak kod otomatik olarak tespit edilir. Önceki sürümdeyken bile kodunuz bozulacaksa uyarı görürsünüz.
Çözüm, geri dönüş türü kullanılmasın diye türü açıkça belirtmektir. Ne yazık ki hangi türün yazılması gerektiğini görmek her zaman kolay olmayabilir.
Bu değişiklikle bozulan en yaygın kalıplardan biri, f fonksiyonu dönüş
türünün Ok kısmı üzerinde jenerikken f()?; kullanımıdır:
#![allow(unused)]
fn main() {
#![allow(dependency_on_unit_never_type_fallback)]
fn outer<T>(x: T) -> Result<T, ()> {
fn f<T: Default>() -> Result<T, ()> {
Ok(T::default())
}
f()?;
Ok(x)
}
}
Bu örnekte T türünün çıkarılamayacağını düşünebilirsiniz. Ancak ? işlecin
şu anki sözdizimsel açılımı nedeniyle tür önceden () olarak çıkarılıyordu;
artık ! olarak çıkarılacak.
Sorunu çözmek için T türünü açıkça belirtmelisiniz:
#![allow(unused)]
fn main() {
#![deny(dependency_on_unit_never_type_fallback)]
fn outer<T>(x: T) -> Result<T, ()> {
fn f<T: Default>() -> Result<T, ()> {
Ok(T::default())
}
f::<()>()?;
// ...ya da:
() = f()?;
Ok(x)
}
}
Bir diğer görece yaygın durum, kapanış içinde panic üretmektir:
#![allow(unused)]
fn main() {
#![allow(dependency_on_unit_never_type_fallback)]
trait Unit {}
impl Unit for () {}
fn run<R: Unit>(f: impl FnOnce() -> R) {
f();
}
run(|| panic!());
}
Daha önce panic! içinden gelen !, Unit uygulayan () türüne zorlanıyordu.
Artık ! olduğu gibi kaldığı için bu kod, ! Unit uygulamadığından
başarısız olur. Bunu çözmek için kapanışın dönüş türünü açıkça belirtebilirsiniz:
#![allow(unused)]
fn main() {
#![deny(dependency_on_unit_never_type_fallback)]
trait Unit {}
impl Unit for () {}
fn run<R: Unit>(f: impl FnOnce() -> R) {
f();
}
run(|| -> () { panic!() });
}
f()? örneğine benzer bir durum, bir dalda ! türünde ifade ve diğer dalda
dönüş türü kısıtlanmamış bir fonksiyon kullanıldığında da görülür:
#![allow(unused)]
fn main() {
#![allow(dependency_on_unit_never_type_fallback)]
if true {
Default::default()
} else {
return
};
}
Önceden return içinden gelen ! yanlış şekilde () türüne zorlandığı için
Default::default() dönüş türü olarak () çıkarılıyordu. Artık bunun yerine
! çıkarılacak; bu yüzden ! Default uygulamadığından kod derlenmeyecektir.
Yine çözüm, türü açıkça belirtmektir:
#![allow(unused)]
fn main() {
#![deny(dependency_on_unit_never_type_fallback)]
() = if true {
Default::default()
} else {
return
};
// ...ya da:
if true {
<() as Default>::default()
} else {
return
};
}