Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

RPIT Ömür Yakalama Kuralları

Bu bölüm, RFC 3498 ile gelen 2024 Ömür Yakalama Kuralları ile ilgili değişiklikleri açıklar. Ayrıca kodunuzu taşırken opak türlerde kesin yakalama özelliğini, yani RFC 3617 ile gelen yaklaşımı nasıl kullanacağınızı da anlatır.

Özet

  • Rust 2024’te, use<..> sınırı yoksa ömür parametreleri dahil kapsam içindeki tüm jenerik parametreler örtük olarak yakalanır.
  • Captures hilesi (Captures<..> sınırları) ve outlives hilesi (örneğin '_ sınırları), use<..> sınırlarıyla değiştirilebilir ya da Rust 2024’te tamamen kaldırılabilir.

Ayrıntılar

Yakalama

Bir RPIT (return-position impl Trait) opak türünde bir jenerik parametreyi yakalamak, ilgili gizli tür içinde o parametrenin kullanılabilmesini sağlar. Rust 1.82’de hangi jenerik parametrelerin yakalanacağını açıkça belirtmeye yarayan use<..> sınırları eklendi. Bunlar hem kodunuzu Rust 2024’e taşırken işe yarar hem de bu bölümde sürüme özgü örtük yakalama kurallarını açıklamayı kolaylaştırır. use<..> sınırları şu şekilde görünür:

#![allow(unused)]
fn main() {
fn capture<'a, T>(x: &'a (), y: T) -> impl Sized + use<'a, T> {
    //                                ~~~~~~~~~~~~~~~~~~~~~~~
    //                             Bu, RPIT opak türüdür.
    //
    //                                `'a` ile `T`yi yakalar.
    (x, y)
  //~~~~~~
  // Gizli tür şudur: `(&'a (), T)`.
  //
  // Bu tür `'a` ile `T`yi kullanabilir; çünkü yakalanmışlardır.
}
}

Yakalanan jenerik parametreler, opak türün nasıl kullanılabileceğini etkiler. Örneğin aşağıdaki kullanım hatadır; çünkü gizli tür ömrü kullanmasa bile ömür yakalanmıştır:

#![allow(unused)]
fn main() {
fn capture<'a>(_: &'a ()) -> impl Sized + use<'a> {}

fn test<'a>(x: &'a ()) -> impl Sized + 'static {
    capture(x)
    //~^ HATA ömür yeterince uzun yaşamıyor olabilir
}
}

Buna karşılık şu kullanım geçerlidir:

#![allow(unused)]
fn main() {
fn capture<'a>(_: &'a ()) -> impl Sized + use<> {}

fn test<'a>(x: &'a ()) -> impl Sized + 'static {
    capture(x) //~ TAMAM
}
}

use<..> sınırı yokken sürüme özgü kurallar

use<..> sınırı yoksa, derleyici kapsam içindeki hangi jenerik parametrelerin örtük biçimde yakalanacağına karar vermek için sürüme özgü kuralları kullanır.

Tüm sürümlerde, use<..> sınırı yoksa kapsam içindeki tüm tür ve const jenerik parametreleri örtük olarak yakalanır. Örneğin:

#![allow(unused)]
fn main() {
fn f_implicit<T, const C: usize>() -> impl Sized {}
//                                    ~~~~~~~~~~
//                         Burada `use<..>` sınırı yok.
//
// Tüm sürümlerde yukarıdaki tanım şuna denktir:
fn f_explicit<T, const C: usize>() -> impl Sized + use<T, C> {}
}

Rust 2021 ve daha eski sürümlerde, use<..> sınırı yokken jenerik ömür parametreleri yalnızca çıplak fonksiyon imzalarındaki ve inherent impl içindeki ilişkili fonksiyon/metod imzalarındaki RPIT opak türlerde bir sınır içinde sözdizimsel olarak göründüklerinde yakalanır. Rust 2024’ten itibaren ise kapsam içindeki bu jenerik ömür parametreleri koşulsuz yakalanır. Örneğin:

#![allow(unused)]
fn main() {
fn f_implicit(_: &()) -> impl Sized {}
// Rust 2021 ve öncesinde yukarıdaki tanım şuna denktir:
fn f_2021(_: &()) -> impl Sized + use<> {}
// Rust 2024 ve sonrasında ise şuna denktir:
fn f_2024(_: &()) -> impl Sized + use<'_> {}
}

Bu değişiklik, trait impl içindeki ilişkili fonksiyon ve metod imzalarındaki RPIT opak türlerle, trait tanımları içindeki RPIT kullanımlarıyla (RPITIT) ve async fn tarafından oluşturulan opak Future türleriyle davranışı tutarlı hale getirir. Bunların hepsi, use<..> sınırı olmadığında tüm sürümlerde kapsam içindeki tüm jenerik ömür parametrelerini örtük olarak yakalar.

Dış jenerik parametreler

Dış bir impl’den gelen jenerik parametreler de örtük olarak neyin yakalanacağına karar verilirken kapsam içinde kabul edilir. Örneğin:

#![allow(unused)]
fn main() {
struct S<T, const C: usize>((T, [(); C]));
impl<T, const C: usize> S<T, C> {
//   ~~~~~~~~~~~~~~~~~
// Bu jenerik parametreler kapsam içindedir.
    fn f_implicit<U>() -> impl Sized {}
    //            ~       ~~~~~~~~~~
    //            ^ Bu jenerik de kapsam içindedir.
    //                    ^
    //                    |
    //     Burada `use<..>` sınırı yok.
    //
    // Tüm sürümlerde şuna denktir:
    fn f_explicit<U>() -> impl Sized + use<T, U, C> {}
}
}

Higher-ranked bağlayıcılardan gelen ömürler

Aynı şekilde, higher-ranked bir for<..> bağlayıcısı tarafından kapsama sokulan jenerik ömür parametreleri de kapsam içinde kabul edilir. Örneğin:

#![allow(unused)]
fn main() {
trait Tr<'a> { type Ty; }
impl Tr<'_> for () { type Ty = (); }

fn f_implicit() -> impl for<'a> Tr<'a, Ty = impl Copy> {}
// Rust 2021 ve öncesinde yukarıdaki tanım şuna denktir:
fn f_2021() -> impl for<'a> Tr<'a, Ty = impl Copy + use<>> {}
// Rust 2024 ve sonrasında ise şuna denktir:
//fn f_2024() -> impl for<'a> Tr<'a, Ty = impl Copy + use<'a>> {}
//                                        ~~~~~~~~~~~~~~~~~~~~
// Ancak iç içe opak türlerde higher-ranked ömürlerin
// yakalanması henüz desteklenmiyor.
}

Argüman konumundaki impl Trait (APIT)

APIT (argument position impl Trait) kullanımıyla oluşturulan anonim, yani adsız jenerik parametreler de kapsam içinde kabul edilir. Örneğin:

#![allow(unused)]
fn main() {
fn f_implicit(_: impl Sized) -> impl Sized {}
//               ~~~~~~~~~~
//           Buna APIT denir.
//
// Yukarıdaki tanım kabaca şuna denktir:
fn f_explicit<_0: Sized>(_: _0) -> impl Sized + use<_0> {}
}

İlkinin ikincisine tam olarak denk olmadığını unutmayın; çünkü jenerik parametreye ad verdiğinizde artık ona turbofish sözdizimiyle açıkça argüman verilebilir. Adsız bir jenerik parametreyi use<..> sınırına açık biçimde eklemenin, onu adlandırılmış jenerik parametreye çevirmek dışında bir yolu yoktur.

Taşıma

Fazla yakalamadan kaçınarak taşıma

impl_trait_overcaptures lint’i, Rust 2024’te ek ömürler yakalayacak RPIT opak türleri işaretler. Bu lint, cargo fix --edition çalıştırıldığında otomatik olarak uygulanan rust-2024-compatibility lint grubunun parçasıdır. Çoğu durumda lint, Rust 2024’te fazladan ömür yakalanmaması için gereken yerlere otomatik olarak use<..> sınırları ekleyebilir.

Kodunuzu Rust 2024 ile uyumlu hale getirmek için şunu çalıştırın:

cargo fix --edition

Örneğin bu komut şunu:

#![allow(unused)]
fn main() {
fn f<'a>(x: &'a ()) -> impl Sized { *x }
}

şuna dönüştürür:

#![allow(unused)]
fn main() {
fn f<'a>(x: &'a ()) -> impl Sized + use<> { *x }
}

Bu use<> sınırı eklenmezse, Rust 2024’te opak tür 'a ömür parametresini yakalar. Bu sınır eklenerek taşıma lint’i mevcut anlamı korur.

APIT içeren durumları taşıma

Bazı durumlarda lint, değişikliği otomatik yapamaz; çünkü use<..> sınırı içinde yer alabilmesi için bir jenerik parametreye ad verilmesi gerekir. Böyle durumlarda lint, elle bir değişiklik yapmanız gerekebileceğini bildirir. Örneğin:

#![allow(unused)]
fn main() {
fn f<'a>(x: &'a (), y: impl Sized) -> impl Sized { (*x, y) }
//   ^^                ~~~~~~~~~~
//               Bu bir APIT kullanımıdır.
//
//~^ UYARI `impl Sized`, 2024 sürümünde istenenden daha fazla ömür yakalayacak olabilir
//~| NOT özellikle bu ömür kapsam içinde ama türün sınırlarında belirtilmiyor

fn test<'a>(x: &'a (), y: ()) -> impl Sized + 'static {
    f(x, y)
}
}

Bu kod, APIT kullanımı nedeniyle ve jenerik tür parametresinin use<..> sınırında adlandırılması gerektiği için otomatik olarak dönüştürülemez. Bu kodu ömrü yakalamadan Rust 2024’e çevirmek için tür parametresine ad vermelisiniz. Örneğin:

#![allow(unused)]
fn main() {
#![deny(impl_trait_overcaptures)]
fn f<'a, T: Sized>(x: &'a (), y: T) -> impl Sized + use<T> { (*x, y) }
//       ~~~~~~~~
// Tür parametresi burada adlandırıldı.

fn test<'a>(x: &'a (), y: ()) -> impl Sized + use<> {
    f(x, y)
}
}

Bunun fonksiyon API’sini az da olsa değiştirdiğini unutmayın; çünkü artık bu parametre için turbofish sözdizimiyle açık tür argümanı verilebilir. Bu istenmiyorsa, use<..> sınırını eklememeyi ve ömrün yakalanmasına izin vermeyi de düşünebilirsiniz. Özellikle ileride bu ömrü gizli tür içinde kullanmak isteyebileceğinizi düşünüyorsanız bu daha uygun olabilir.

Captures hilesinden vazgeçerek taşıma

Rust 1.82’de gelen kesin yakalama use<..> sınırlarından önce, bir RPIT opak türde bir ömrü doğru biçimde yakalamak çoğu zaman Captures hilesini gerektirirdi. Örneğin:

#![allow(unused)]
fn main() {
#[doc(hidden)]
pub trait Captures<T: ?Sized> {}
impl<T: ?Sized, U: ?Sized> Captures<T> for U {}

fn f<'a, T>(x: &'a (), y: T) -> impl Sized + Captures<(&'a (), T)> {
//                                           ~~~~~~~~~~~~~~~~~~~~~
//                            Buna `Captures` hilesi denir.
    (x, y)
}

fn test<'t, 'x>(t: &'t (), x: &'x ()) {
    f(t, x);
}
}

use<..> sınırı sözdizimiyle birlikte Captures hilesine artık gerek kalmadı. Tüm sürümlerde şu biçimle değiştirilebilir:

#![allow(unused)]
fn main() {
fn f<'a, T>(x: &'a (), y: T) -> impl Sized + use<'a, T> {
    (x, y)
}

fn test<'t, 'x>(t: &'t (), x: &'x ()) {
    f(t, x);
}
}

Rust 2024’te use<..> sınırı çoğu zaman tamamen atlanabilir ve yukarıdaki örnek basitçe şöyle yazılabilir:

#![allow(unused)]
fn main() {
fn f<'a, T>(x: &'a (), y: T) -> impl Sized {
    (x, y)
}

fn test<'t, 'x>(t: &'t (), x: &'x ()) {
    f(t, x);
}
}

Bunun için otomatik taşıma yoktur ve Captures hilesi Rust 2024’te hâlâ çalışır; ancak bu eski hileden elle uzaklaşmayı düşünmek isteyebilirsiniz.

Outlives hilesinden vazgeçerek taşıma

Rust 1.82’de kesin yakalama use<..> sınırları gelmeden önce, bir ömrün bir opak türün gizli türünde kullanılması gerektiğinde “outlives hilesi” sıkça kullanılırdı. Örneğin:

#![allow(unused)]
fn main() {
fn f<'a, T: 'a>(x: &'a (), y: T) -> impl Sized + 'a {
    //    ~~~~                                 ~~~~
    //    ^                     Bu, outlives hilesidir.
    //    |
    // Bu sınır yalnızca bu hile için gereklidir.
    (x, y)
//  ~~~~~~
// Gizli tür `(&'a (), T)`dir.
}
}

Bu hile, Captures hilesine göre daha az dolambaçlıydı; ama aynı zamanda daha az doğruydu. Yukarıdaki örnekte gördüğümüz gibi, T içindeki ömür bileşenleri 'a ömründen bağımsız olsa bile bu hileyi çalıştırmak için T: 'a sınırı eklemek zorunda kalıyoruz. Bu da çağıranlar üzerinde gereksiz ve şaşırtıcı kısıtlamalar yaratıyordu.

Kesin yakalama kullanıldığında, yukarıdaki örnek tüm sürümlerde şu şekilde yazılabilir:

#![allow(unused)]
fn main() {
fn f<T>(x: &(), y: T) -> impl Sized + use<'_, T> {
    (x, y)
}

fn test<'t, 'x>(t: &'t (), x: &'x ()) {
   f(t, x);
}
}

Rust 2024’te use<..> sınırı çoğu zaman tamamen atlanabilir ve yukarıdaki örnek basitçe şöyle yazılabilir:

#![allow(unused)]
fn main() {
fn f<T>(x: &(), y: T) -> impl Sized {
    (x, y)
}

fn test<'t, 'x>(t: &'t (), x: &'x ()) {
   f(t, x);
}
}

Bunun için otomatik taşıma yoktur ve outlives hilesi Rust 2024’te hâlâ çalışır; ama bu eski hileden elle uzaklaşmayı düşünmek isteyebilirsiniz.