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

Giriş

Rust Sürümleri Rehberi’ne hoş geldiniz! “Sürümler” (editions), Rust’ın dile normalde geriye dönük uyumluluğu bozacak değişiklikleri ekleme yöntemidir.

Bu rehberde şunları ele alacağız:

  • Sürümlerin ne olduğunu
  • Her sürümde hangi değişikliklerin yer aldığını
  • Kodunuzu bir sürümden diğerine nasıl taşıyacağınızı

Sürümler Nedir?

Mayis 2015’te Rust 1.0’in yayinlanmasi, Rust’in temel ilkelerinden biri olarak “duraganlik olmadan kararlilik” anlayisini yerlesik hale getirdi. O gunden bu yana Rust su onemli kurala bagli kaliyor: Bir ozellik kararli kanal uzerinden yayinlandiginda, gelecekteki tum surumlerde de desteklenmeye devam edilir.

Yine de bazen dile geriye uyumsuz degisiklikler yapmak faydali olabilir. Bunun yaygin bir ornegi yeni bir anahtar kelime eklenmesidir. Ornegin Rust’in ilk surumlerinde async ve await anahtar kelimeleri yoktu.

Rust bu yeni anahtar kelimeleri bir anda ekleseydi bazi kodlar bozulurdu: let async = 1; artik calismazdi.

Rust bu sorunu cozmek icin surumleri kullanir. Geriye uyumsuz degisiklikler gerektiginde bunlar bir sonraki surume alinir. Surumler istege bagli oldugu icin, var olan crate’ler acikca yeni surume tasinmadikca bu degisiklikleri kullanmaz. Ornegin Rust’in en guncel surumu, 2018 veya sonrasi bir surum secilmedigi surece async kelimesini anahtar kelime olarak ele almaz.

Her crate, kendi Cargo.toml dosyasi icinde hangi surumu kullanacagini secer. Cargo ile yeni bir crate olusturuldugunda, kararli durumdaki en yeni surum otomatik olarak secilir.

Sürümler ekosistemi bölmez

Surumler tasarlanirken en kritik kural sudur: bir surumdeki crate’ler, diger surumlerle derlenmis crate’lerle sorunsuz bicimde birlikte calismalidir.

Baska bir deyişle, her crate yeni bir surume ne zaman gececegine bagimsiz olarak karar verebilir. Bu karar “ozel“dir; ekosistemdeki diger crate’leri etkilemez.

Rust acisindan bu uyumluluk, bir surumde yapilabilecek degisikliklerin turune de belli sinirlar koyar. Bu yuzden yeni Rust surumlerindeki degisiklikler genelde “yuzeysel” kalir. Rust kodunun tamami, hangi surum kullanilirsa kullanilsin, sonunda derleyici icinde ayni ic temsile donusur.

Sürüm taşıma işlemi kolaydır ve büyük ölçüde otomatiktir

Rust, yeni bir surume gecisi kolay bir surece donusturmeyi hedefler. Yeni bir surum yayinlandiginda crate yazarlar, cargo icindeki otomatik tasima araclarini kullanarak gecis yapabilir. Cargo daha sonra kodu yeni surumle uyumlu hale getirmek icin kucuk duzeltmeler uygular.

Ornegin Rust 2018’e geciste, async adini tasiyan her sey ham tanimlayici sozdizimi ile r#async bicimine donusturulur.

Cargo’nun otomatik tasimalari kusursuz degildir; bazi kose durumlarda elle degisiklik yapmak yine de gerekebilir. Bu araclar, kodun dogrulugunu ya da performansini etkileyebilecek anlamsal degisikliklerden kacinmayi hedefler.

Bu rehber neleri kapsar?

Bu Rust Surum Rehberi, araclara ek olarak her surumun parcasi olan degisiklikleri de kapsar. Her degisikligi aciklar ve varsa ek ayrintilara baglanti verir. Ayrica crate yazarlarinin dikkat etmesi gereken kose durumlari ve zor ayrintilari da ele alir.

Crate yazarlarinin burada su basliklari bulmasi beklenir:

  • Surumlere genel bakis
  • Belirli surumler icin tasima rehberi
  • Otomatik araclar yetersiz kaldiginda hizli bir sorun giderme basvurusu

Yeni bir proje oluşturma

Cargo ile olusturulan yeni bir proje, varsayilan olarak en yeni surumu kullanacak sekilde ayarlanir:

$ cargo new ornek
    Creating binary (application) `ornek` package
note: see more `Cargo.toml` keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
$ cat ornek/Cargo.toml
[package]
name = "ornek"
version = "0.1.0"
edition = "2024"

[dependencies]

Buradaki edition = "2024" ayari, paketin Rust 2024 surumu ile derlenecegini belirler. Ek bir ayar gerekmez.

Projeyi belirli bir surumle olusturmak icin cargo new komutunun --edition <YEAR> secenegini kullanabilirsiniz. Ornegin Rust 2018 surumunu kullanan yeni bir proje olusturmak su sekilde yapilabilir:

$ cargo new --edition 2018 ornek
    Creating binary (application) `ornek` package
note: see more `Cargo.toml` keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
$ cat ornek/Cargo.toml
[package]
name = "ornek"
version = "0.1.0"
edition = "2018"

[dependencies]

Surum yili icin gecersiz bir deger yazarsaniz endiselenmeyin; cargo new gecersiz bir surum yilini kabul etmez:

$ cargo new --edition 2019 ornek
error: invalid value '2019' for '--edition <YEAR>'
  [possible values: 2015, 2018, 2021, 2024]

  tip: a similar value exists: '2021'

For more information, try '--help'.

edition anahtarinin degerini, Cargo.toml dosyasini duzenleyerek kolayca degistirebilirsiniz. Ornegin paketinizi Rust 2015 surumuyle derletmek icin anahtari su sekilde ayarlarsiniz:

[package]
name = "ornek"
version = "0.1.0"
edition = "2015"

[dependencies]

Var olan bir projeyi yeni bir sürüme taşımak

Rust, bir projeyi bir surumden sonrakine otomatik olarak tasimaya yardim eden araclar sunar. Bu araclar kaynak kodunuzu bir sonraki surumle uyumlu olacak sekilde gunceller.

Kisaca, bir sonraki surume gecmek icin adimlar sunlardir:

  1. Bagimliliklarinizi en guncel surumlere yuklemek icin cargo update calistirin.
  2. cargo fix --edition calistirin.
  3. Cargo.toml dosyasini duzenleyip edition alanini bir sonraki surume ayarlayin; ornegin edition = "2024".
  4. Duzeltmelerin ise yaradigini dogrulamak icin cargo build veya cargo test calistirin.
  5. Projeyi yeniden bicimlendirmek icin cargo fmt calistirin.

Asagidaki bolumler bu adimlarin ayrintilarina ve yolda karsilasabileceginiz bazi sorunlara daha yakindan bakar.

Amacimiz, yeni surumlere gecisin olabildigince sorunsuz olmasidir. En yeni surume yukseltmek sizin icin zorsa, bunu bir hata olarak goruyoruz. Bu surecte bir sorunla karsilasirsaniz lutfen bir hata bildirimi olusturun.

Taşımaya başlamak

Ornek olarak 2015 surumunden 2018 surumune gecise bakalim. 2021 gibi baska surumlere geciste de adimlar temelde aynidir.

Elimizde src/lib.rs icinde su kodun oldugu bir crate oldugunu dusunun:

#![allow(unused)]
fn main() {
trait Ozellik {
    fn islem(&self, i32);
}
}

Bu kod adsiz bir parametre, yani i32, kullaniyor. Bu yapi Rust 2018’de desteklenmez, bu yuzden kod derlenmez. Haydi bunu guncel hale getirelim.

Bağımlılıkları güncelleme

Baslamadan once bagimliliklarinizi guncellemeniz tavsiye edilir. Ozellikle bazi proc-macro’lar veya derleme zamaninda kod ureten bagimliliklar, yeni surumlerle uyumluluk sorunu yasayabilir. Son guncellemeden bu yana bu sorunlari gideren yeni yayinlar cikmis olabilir. Su komutu calistirin:

cargo update

Guncellemeden sonra her seyin calistigini dogrulamak icin testlerinizi calistirmak isteyebilirsiniz. git gibi bir surum kontrol araci kullaniyorsaniz, mantiksal ayrimi korumak icin bu degisiklikleri ayri bir commit olarak kaydetmek isteyebilirsiniz.

Kodu yeni sürümle uyumlu hale getirme

Kodunuz yeni surumle uyumsuz ozellikler kullaniyor da olabilir, kullanmiyor da. Bir sonraki surume gecisi kolaylastirmak icin Cargo, kaynak kodunuzu otomatik olarak guncelleyebilen cargo fix alt komutunu sunar. Baslamak icin sunu calistiralim:

cargo fix --edition

Bu komut kodunuzu denetler ve duzeltebildigi sorunlari otomatik olarak duzeltir. src/lib.rs dosyasina tekrar bakalim:

#![allow(unused)]
fn main() {
trait Ozellik {
    fn islem(&self, _: i32);
}
}

Kod, bu i32 degeri icin bir parametre adi eklenecek sekilde yeniden yazildi. Bu ornekte daha once bir ad olmadigi icin cargo fix, kullanilmayan degiskenlerde geleneksel oldugu uzere _ koydu.

cargo fix kodunuzu her zaman otomatik olarak duzeltemez. Bir seyi duzeltemezse konsola bununla ilgili bir uyari yazar. Boyle bir uyari gorurseniz kodu elle guncellemeniz gerekir. Tasima sureciyle calismaya dair daha fazla ayrinti icin Gelişmis taşıma stratejileri bolumune bakin; hangi degisikliklerin gerekli oldugunu aciklayan ilgili rehber bolumlerini de okuyun. Sorun yasarsaniz kullanici forumlarindan yardim isteyebilirsiniz.

Yeni özellikleri kullanmak için yeni sürümü etkinleştirme

Bazi yeni ozellikleri kullanabilmek icin yeni surume acikca gecmeniz gerekir. Devam etmeye hazir oldugunuzda Cargo.toml dosyanizi duzenleyip yeni edition anahtar/deger ciftini ekleyin. Ornegin:

[package]
name = "ornek"
version = "0.1.0"
edition = "2018"

Eger edition anahtari yoksa Cargo varsayilan olarak Rust 2015’i kullanir. Ama bu ornekte 2018 secildigi icin kodumuz Rust 2018 ile derlenecektir.

Yeni sürümde kodu test etme

Siradaki adim, projenizi yeni surumde test etmektir. Her seyin hala dogru calistigini dogrulamak icin proje testlerinizi, ornegin cargo test kullanarak, calistirin. Yeni uyarilar cikarsa, derleyicinin onerilerini uygulamak icin cargo fix komutunu --edition bayragi olmadan yeniden calistirmayi dusunebilirsiniz.

Bu noktada yine de bazi elle degisiklikler gerekebilir. Ornegin otomatik tasima doctest’leri guncellemez; derleme zamaninda kod ureten yapilar veya makrolar da elle guncellenmek isteyebilir. Daha fazla bilgi icin [gelismis tasimalar bolumune] bakin.

Tebrikler. Kodunuz artik hem Rust 2015 hem de Rust 2018 icin gecerli.

Rustfmt ile yeniden biçimlendirme

Eger projenizde bicimlendirmeyi otomatik korumak icin rustfmt kullaniyorsaniz, yeni surumun bicimlendirme kurallarini kullanarak kodu yeniden bicimlendirmeyi dusunmelisiniz.

Yeniden bicimlendirmeden once, eger git gibi bir surum kontrol araci kullaniyorsaniz, bu adımdan onceye kadar yaptiginiz tum degisiklikleri commit etmek isteyebilirsiniz. Bicimlendirme degisikliklerini ayri bir commit’te tutmak faydali olabilir; boylece hangi degisikliklerin sadece bicimlendirme, hangilerinin ise gercek kod degisikligi oldugunu daha kolay gorebilirsiniz. Bu ayrim git blame kullanirken de ise yarar.

cargo fmt

Daha fazla bilgi icin [stil surumleri bolumune] bakin.

Kararsız bir sürüme geçiş

Bir surum yayinlandiktan sonra, bir sonraki surume kadar yaklasik uc yillik bir zaman araligi olur. Bu surede bir sonraki surume yeni ozellikler eklenebilir; bu ozellikler yalnizca nightly kanalinda kullanilabilir. Bu ozellikler kararli hale gelmeden once test etmeye yardim etmek isterseniz nightly kanalini kullanabilirsiniz.

Adimlar kararli kanaldakine kabaca benzer:

  1. En guncel nightly surumu kurun: rustup update nightly.
  2. cargo +nightly fix --edition calistirin.
  3. Cargo.toml dosyasini duzenleyip en uste ([package] bolumunun ustune) cargo-features = ["edition20xx"] ekleyin ve edition alanini gecmek istediginiz surumu gosterecek sekilde edition = "20xx" yapin.
  4. Yeni surumde calistigini dogrulamak icin cargo +nightly check calistirin.

⚠ Dikkat: Bir sonraki surumde uygulanacak ozellikler icin cargo fix tarafinda otomatik tasimalar henuz bulunmayabilir; ozelliklerin kendisi de tamamlanmamis olabilir. Mumkun oldugunda bu rehber, nightly’de hangi ozelliklerin uygulanmis oldugunu ve durumlarini aciklamalidir. Surum kararli hale gelmeden birkac ay once, tum yeni ozelliklerin tamamlanmis olmasi beklenir ve Rust Blog test cagrisi yapar.

Gelişmiş taşıma stratejileri

Taşımalar nasıl çalışır?

cargo fix --edition, projenizde cargo check komutuna denk bir calistirma yapip, bir sonraki surumde derlenmeyebilecek kodu tespit eden ozel lintleri etkinlestirerek calisir. Bu lint’ler, kodu hem mevcut surumle hem de sonraki surumle uyumlu hale getirmek icin nasil degistirmeniz gerektigine dair yonlendirmeler icerir. cargo fix bu degisiklikleri kaynak koda uygular, ardindan duzeltmelerin ise yaradigini dogrulamak icin cargo check komutunu yeniden calistirir. Duzeltmeler basarisiz olursa yaptigi degisiklikleri geri alir ve bir uyari gosterir.

Kodu hem mevcut hem de sonraki surumle ayni anda uyumlu hale getirmek, tasimayi parca parca yapmayi kolaylastirir. Otomatik tasima tamamen basarili olmazsa ya da elle mudahale isterse, Cargo.toml dosyasini henuz degistirmeden once orijinal surumde kalarak adim adim ilerleyebilirsiniz.

cargo fix --edition tarafindan uygulanan lint’ler bir lint grubunun parcasidir. Ornegin 2018’den 2021’e gecerken Cargo, kodu duzeltmek icin rust-2021-compatibility lint grubunu kullanir. Tasimada tek tek lint kullanmayla ilgili ipuclari icin asagidaki Kirik kodla parcali tasima bolumune bakin.

cargo fix, cargo check komutunu birden fazla kez calistirabilir. Ornegin bir duzeltme kumesi uygulandiktan sonra, yeni uyarilar ortaya cikabilir ve ek duzeltmeler gerekebilir. Cargo bu sureci, yeni uyari uretilmeyene kadar tekrarlar.

Birden çok yapılandırmayı taşımak

cargo fix ayni anda yalnizca tek bir yapilandirmayla calisabilir. Cargo features veya kosullu derleme kullaniyorsaniz, farkli bayraklarla cargo fix komutunu birden fazla kez calistirmaniz gerekebilir.

Ornegin farkli platformlarda farkli kodu dahil etmek icin #[cfg] ozniteliklerini kullanan bir kodunuz varsa, farkli hedefleri duzeltmek icin cargo fix komutunu --target secenegiyle calistirmaniz gerekebilir. Elinizde capraz derleme yoksa bu, kodunuzu makineler arasinda tasimanizi da gerektirebilir.

Benzer bicimde, #[cfg(feature = "benim-istege-bagli-seyim")] gibi Cargo ozelliklerine bagli kosullariniz varsa, bu ozellik kapilarinin arkasindaki tum kodu da tasiyabilmek icin --all-features bayragini kullanmaniz tavsiye edilir. Ozellik kodunu tek tek tasimak istiyorsaniz --features bayragiyla her birini ayri ayri ele alabilirsiniz.

Büyük bir projeyi veya çalışma alanını taşımak

Sorunlarla karsilastiginizda sureci kolaylastirmak icin buyuk bir projeyi parca parca tasiyabilirsiniz.

Bir Cargo çalışma alanında her paket kendi surumunu tanimlar; bu yuzden surec dogal olarak paketleri tek tek tasimayi destekler.

Bir Cargo paketi icinde ise tum paketi bir anda tasiyabilir ya da tek tek Cargo hedeflerini tasiyabilirsiniz. Ornegin birden fazla ikili, test ve ornek varsa, cargo fix --edition ile yalnizca belirli bir hedefi tasimak icin hedef secim bayraklarini kullanabilirsiniz. Varsayilan olarak cargo fix, --all-targets kullanir.

Daha da ileri durumlar icin, Cargo.toml icinde her hedefin surumunu ayri ayri su sekilde belirleyebilirsiniz:

[[bin]]
name = "benim-ikilim"
edition = "2018"

Bu genelde gerekli olmaz; ancak cok sayida hedefiniz varsa ve hepsini bir arada tasimakta zorlanıyorsaniz ise yarayan bir secenektir.

Kırık kodla parçalı taşıma

Bazen derleyicinin onerdigi duzeltmeler beklendigi gibi calismaz. Boyle bir durumda Cargo ne oldugunu ve hangi hatayla karsilasildigini bildiren bir uyari yazar. Ancak varsayilan olarak yaptigi degisiklikleri otomatik geri alir. Kodu kirik halde birakip sorunu elle cozmek bazen daha faydali olur. Bazi duzeltmeler dogru olabilir; kirik olan duzeltme de buyuk olcude dogru olup sadece kucuk bir el ayari gerektiriyor olabilir.

Bu durumda Cargo’ya degisiklikleri geri almamasini soylemek icin cargo fix ile birlikte --broken-code secenegini kullanin. Sonra hatayi elle inceleyip neyin duzeltilmesi gerektigini arastirabilirsiniz.

Bir projeyi adim adim tasimanin baska bir yolu da duzeltmeleri tek tek uygulamaktir. Bunun icin tekil lint’leri uyari olarak ekleyebilir, ardindan ya cargo fix komutunu (--edition olmadan) calistirabilir ya da editorunuz/IDE’niz “Hizli Duzeltme” benzeri bir ozellik sunuyorsa onun onerilerini uygulayabilirsiniz.

Ornegin 2018 surumu, cakisan anahtar kelimeleri duzeltmek icin keyword-idents lint’ini kullanir. Her crate’in en ustune (src/lib.rs veya src/main.rs basina) #![warn(keyword_idents)] ekleyebilirsiniz. Ardindan cargo fix yalnizca bu lint’in onerilerini uygular.

Her surum icin hangi lint’lerin etkinlestirildigini lint grubu sayfasindan gorebilir veya rustc -Whelp komutunu calistirabilirsiniz.

Makroları taşımak

Bazi makrolar, bir sonraki surum icin duzeltilirken elle mudahale gerektirebilir. Ornegin cargo fix --edition, bir sonraki surumde gecersiz olacak sozdizimini ureten bir makroyu otomatik olarak duzeltemeyebilir.

Bu hem proc macrolar hem de macro_rules tarzindaki makrolar icin sorun olabilir. macro_rules makrolari bazen ayni crate icinde kullaniliyorsa otomatik guncellenebilir, ama bunun mumkun olmadigi cesitli durumlar vardir. Proc macro’lar ise genel olarak hic otomatik duzeltilemez.

Ornegin, 2015’ten 2018’e tasidigimiz bir crate icinde su yapay foo makrosunun bulundugunu varsayalim. foo otomatik olarak duzeltilmez:

#![allow(unused)]
fn main() {
#[macro_export]
macro_rules! foo {
    () => {
        let dyn = 1;
        println!("deger {}", dyn);
    };
}
}

Bu makro 2015 surumlu bir crate icinde tanimlandiginda, makro hijyeni (asagida anlatiliyor) sayesinde diger tum surumlerdeki crate’lerden de kullanilabilir. 2015’te dyn normal bir tanimlayicidir ve kisitsiz kullanilabilir.

Ancak 2018’de dyn artik gecerli bir tanimlayici degildir. 2018’e gecmek icin cargo fix --edition kullandiginizda Cargo hic uyari ya da hata gostermeyebilir. Yine de foo, herhangi bir crate icinden cagrildiginda calismayacaktir.

Makrolariniz varsa, makronun sozdizimini tam olarak kapsayan testlerinizin olmasi tavsiye edilir. Ayrica makrolari farkli surumlerdeki crate’lere aktarip kullanarak her yerde dogru calistiklarini test etmek de iyi olur. Bir sorunla karsilasirsaniz, bu rehberin ilgili bolumlerini okuyup kodun tum surumlerde calisacak sekilde nasil degistirilecegini anlamaniz gerekir.

Makro hijyeni

Makrolar, iclerindeki token’lari hangi surumden geldiklerini isaretleyerek “surum hijyeni” denen bir sistem kullanir. Bu sayede harici makrolar, hangi surumden cagrildigini dert etmeden farkli surumlerdeki crate’lerden cagrilabilir.

Yukaridaki ornekte dyn adini tanimlayici olarak kullanan macro_rules makrosuna daha yakindan bakalim. Eger bu makro 2015 surumunu kullanan bir crate’te tanimlandiysa, dyn 2018 crate’inde anahtar kelime olsa bile, normal sartlarda sozdizim hatasi verecek bu kod yine de calisir. let dyn = 1; token’lari 2015’e ait olarak isaretlenir ve derleyici bu bilgi genisleme nerede olursa olsun hatirlar. Ayriştirici, bunu nasil yorumlayacagini token’in surumune bakarak anlar.

Sorun, makronun tanimlandigi crate’in surumu 2018’e cevrildiginde ortaya cikar. Bu kez token’lar 2018 etiketi tasir ve artik ayrıştırılamaz. Ama makroyu kendi crate’imizde hic cagrimadigimiz icin cargo fix --edition, makroyu inceleme ve duzeltme firsati bulamaz.

Belgelendirme testleri

Su an cargo fix, belgelendirme testlerini guncelleyemez. Cargo.toml icinde surumu guncelledikten sonra her seyin hala gectiginden emin olmak icin cargo test calistirmalisiniz. Belgelendirme testleriniz yeni surumde desteklenmeyen bir sozdizimi kullaniyorsa bunlari elle guncellemeniz gerekir.

Nadir durumlarda her test icin surumu elle ayarlayabilirsiniz. Ornegin uc ters tirnak uzerinde edition2018 aciklamasini kullanarak rustdoc’a hangi surumu kullanacagini soyleyebilirsiniz.

Üretilen kod

Otomatik duzeltmelerin uygulanamadigi bir baska alan, derleme zamaninda Rust kodu ureten bir build script’iniz olmasidir (Kod uretimi orneklerinden birine bakin). Bu durumda bir sonraki surumde calismayan kod ortaya cikarsa, uyumlu kod uretmesi icin build script’i elle degistirmeniz gerekir.

Cargo kullanmayan projeleri taşımak

Eger projeniz derleme sistemi olarak Cargo kullanmiyorsa bile, bir sonraki surume tasinirken otomatik lint’lerden yardim alma sansiniz olabilir. Bunun icin yukarida aciklandigi gibi uygun lint grubunu etkinlestirebilirsiniz. Ornegin #![warn(rust_2021_compatibility)] ozniteligini ya da -Wrust-2021-compatibility veya --force-warns=rust-2021-compatibility komut satiri bayragini kullanabilirsiniz.

Siradaki adim, bu lint’leri kodunuza uygulamaktir. Bunun icin birkac secenek vardir:

  • Uyarilari elle okuyup derleyicinin onerdigi duzeltmeleri uygulamak.
  • Onerileri otomatik uygulayabilen bir editor veya IDE kullanmak. Ornegin Visual Studio Code, Rust Analyzer eklentisi ile “Hizli Duzeltme” baglantilarini kullanip onerileri otomatik uygulayabilir. Bircok baska editor ve IDE’de de benzer imkanlar vardir.
  • rustfix kutuphanesini kullanarak bir tasima araci yazmak. Bu, Cargo’nun derleyiciden gelen JSON iletilerini alip kaynak kodu degistirmek icin ic tarafta kullandigi kutuphanedir. Kutuphanenin nasil kullanildigina dair ornekler icin examples dizinine bakin.

Yeni bir sürümde deyimsel kod yazmak

Surumler sadece yeni ozellikler eklemek ve eskilerini kaldirmakla ilgili degildir. Her programlama dilinde oldugu gibi deyimler zamanla degisir; Rust da bunun disinda degildir. Eski kod derlenmeye devam etse de bugunun anlayisina gore farkli deyimlerle yazilmis olabilir.

Ornegin Rust 2015’te harici crate’ler su sekilde extern crate ile listelenmelidir:

// src/lib.rs
extern crate rand;

Rust 2018’de bu ogeleri eklemek artik gerekli degildir.

cargo fix, bu deyimlerden bazilarini yeni sozdizimine otomatik olarak tasimak icin --edition-idioms secenegini sunar.

Uyari: Mevcut “deyim lint“lerinin bazi sorunlari oldugu biliniyor. Yanlis oneriler uretebilir ve bu oneriler derlenmeyebilir. Su anki lint’ler sunlardir:

Asagidaki yonlendirmeler, ancak birkac derleyici/Cargo hatasiyla ugrasmayi goze alan kisilere onerilir. Sorun cikarsa, mumkun olan en fazla ilerlemeyi saglamak icin yukarida anlatilan --broken-code secenegini deneyebilir, kalanlari da elle duzeltebilirsiniz.

Bu uyarilari bir kenara koyarsak, Cargo’ya kod parcacigimizi su komutla duzeltmesini soyleyebiliriz:

cargo fix --edition-idioms

Sonrasinda src/lib.rs icindeki extern crate rand; satiri kaldirilir.

Boylece daha deyimsel bir koda gecmis oluruz ve bunu elle yapmak zorunda kalmayiz.

Rust 2015

Rust 2015’in teması “istikrar“dır. Rust 1.0’ın yayımlanmasıyla başladı ve “varsayılan sürüm“dür. Sürüm sistemi 2017’nin sonlarına doğru tasarlanmış olsa da Rust 1.0, 2015 yılının Mayıs ayında yayımlandı. Bu yüzden, geriye dönük uyumluluk nedeniyle herhangi bir sürümü özellikle belirtmediğinizde karşınıza çıkan sürüm 2015 olur.

“İstikrar“ın Rust 2015’in teması olmasının nedeni, 1.0 sürümünün Rust geliştirme sürecinde çok büyük bir dönüm noktası olmasıdır. Rust 1.0’dan önce dil neredeyse her gün değişiyordu. Bu durum, Rust ile büyük yazılımlar geliştirmeyi de öğrenmeyi de zorlaştırıyordu. Rust 1.0 ve Rust 2015’in yayımlanmasıyla birlikte, insanların üzerine proje inşa edebileceği sağlam bir temel sunmak için geriye dönük uyumluluk sözü verdik.

Varsayılan sürüm olduğu için kodunuzu Rust 2015’e taşımak diye bir şey yoktur; o zaten odur. Geçiş yaparken Rust 2015’ten uzaklaşırsınız, ama aslında Rust 2015’e geçmezsiniz. Bu yüzden onun hakkında söylenecek çok fazla başka şey yok!

Rust 2018

Bilgi
RFC#2052, bu RFC sürüm sistemini de öneriyordu
Yayımlanan sürüm1.31.0

Sürüm sistemi, Rust 2018’in yayımlanması için oluşturuldu. Rust 2018 sürümünün yayımlanması, tamamı üretkenlik teması etrafında bir araya getirilen başka özelliklerle aynı döneme denk geldi. Bu özelliklerin büyük bölümü geriye dönük uyumluydu ve artık tüm sürümlerde kullanılabiliyor; ancak bu değişikliklerin bir kısmı sürüm mekanizmasını gerektiriyordu. Bunun en belirgin örneği modül sistemi değişiklikleri oldu.

Yol ve Modül Sistemi Değişiklikleri

Minimum Rust version: 1.31

Özet

  • use bildirimlerindeki yollar artık diğer yollarla aynı şekilde çalışır.
  • :: ile başlayan yollar artık bir harici crate ile devam etmelidir.
  • pub(in path) görünürlük değiştiricilerindeki yollar artık crate, self veya super ile başlamalıdır.

Gerekçe

Modül sistemi, Rust’a yeni başlayanların en çok zorlandığı konulardan biridir. Elbette herkesin öğrenirken zaman alan farklı konuları vardır; ancak bunun bu kadar kafa karıştırıcı gelmesinin temel bir nedeni bulunuyor: Modül sistemini tanımlayan kurallar aslında basit ve tutarlı olsa da sonuçları çoğu zaman tutarsız, sezgiye aykırı ve gizemli görünebilir.

Bu yüzden Rust’ın 2018 sürümü, modül sistemine birkaç yeni özellik getirdi; ama sonuçta olan şey modül sisteminin sadeleşmesi oldu. Böylece neler olduğunu anlamak daha kolay hale geldi.

Kısa bir özet:

  • extern crate, durumların yüzde 99’unda artık gerekli değildir.
  • crate anahtar kelimesi mevcut crate’i ifade eder.
  • Yollar, alt modüllerin içinde bile bir crate adıyla başlayabilir.
  • :: ile başlayan yollar bir harici crate’i göstermelidir.
  • ornek.rs ve ornek/ alt dizini aynı anda var olabilir; alt modülleri bir alt dizine yerleştirirken artık mod.rs gerekmez.
  • use bildirimlerindeki yollar da diğer yollarla aynı şekilde çalışır.

Bu haliyle anlatılınca bunlar keyfi yeni kurallar gibi görünebilir; ama genel resme bakınca zihinsel model artık çok daha sade. Daha fazla ayrıntı için devam edelim.

Daha Fazla Ayrıntı

Şimdi bu yeni özelliklerin her birine sırayla bakalım.

Artık extern crate yok

Bu kısım oldukça açık: Bir crate’i projenize eklemek için artık extern crate yazmanız gerekmiyor. Önceden şöyleydi:

// Rust 2015

extern crate futures;

mod alt_modul {
    use futures::Future;
}

Sonrasında:

// Rust 2018

mod alt_modul {
    use futures::Future;
}

Artık projenize yeni bir crate eklemek için onu Cargo.toml dosyanıza eklemeniz yeterlidir; ikinci bir adım yoktur. Cargo kullanmıyorsanız, zaten rustcye harici crate’lerin konumunu vermek için --extern bayraklarını geçmeniz gerekiyordu; orada da yaptığınız şeyi yapmaya devam edersiniz.

Bir istisna

Bu kuralın bir istisnası vardır: “sysroot” crate’leri. Bunlar Rust’ın kendisiyle birlikte dağıtılan crate’lerdir.

Genelde bunlara yalnızca çok özel durumlarda ihtiyaç duyulur. 1.41 sürümünden itibaren rustc, --extern=CRATE_NAME bayrağını kabul eder. Bu bayrak verilen crate adını, extern crate benzeri bir şekilde otomatik olarak ekler. Derleme araçları bunu sysroot crate’lerini crate’in prelude’una eklemek için kullanabilir. Cargo’nun bunu genel biçimde ifade eden bir yolu yoktur; ancak proc_macro crate’leri için bunu kullanır.

Sysroot crate’lerini açıkça içe aktarmanız gereken bazı durumlar şunlardır:

  • std: Genelde gerekli değildir; çünkü crate #![no_std] ile işaretlenmedikçe std otomatik olarak içe aktarılır.
  • core: Genelde gerekli değildir; çünkü crate #![no_core] ile işaretlenmedikçe core otomatik olarak içe aktarılır. Örneğin standart kütüphanenin kendi iç crate’lerinden bazıları buna ihtiyaç duyar.
  • proc_macro: 1.42’den itibaren bir proc-macro crate’i söz konusuysa Cargo bunu otomatik olarak içe aktarır. Daha eski sürümleri desteklemek istiyorsanız veya rustcye uygun --extern bayraklarını geçmeyen başka bir derleme aracı kullanıyorsanız extern crate proc_macro; gerekebilir.
  • alloc: alloc crate’indeki öğelere genelde std crate’indeki yeniden dışa aktarmalar üzerinden erişilir. Eğer bellek ayırmayı destekleyen bir no_std crate’i ile çalışıyorsanız alloc crate’ini açıkça içe aktarmanız gerekebilir.
  • test: Bu yalnızca [nightly kanalda] kullanılabilir ve genelde kararsız kıyaslama desteği için kullanılır.

Makrolar

extern cratein bir başka kullanım alanı makro içe aktarmaktı; artık buna da gerek yok. Makrolar da diğer öğeler gibi use ile içe aktarılabilir. Örneğin aşağıdaki extern crate kullanımı:

#[macro_use]
extern crate bar;

fn main() {
    baz!();
}

Şunun gibi bir kullanıma dönüştürülebilir:

use bar::baz;

fn main() {
    baz!();
}

Crate adlarını yeniden adlandırma

Eğer crate’i şu şekilde as ile yeniden adlandırıyorsanız:

extern crate futures as f;

use f::Future;

yalnızca extern crate satırını silmek yetmez. Şunu yapmanız gerekir:

use futures as f;

use self::f::Future;

Bu değişiklik, f kullanan her modülde yapılmalıdır.

crate anahtar kelimesi mevcut crate’i ifade eder

use bildirimlerinde ve diğer kodlarda, mevcut crate’in köküne crate:: önekiyle başvurabilirsiniz. Örneğin crate::ornek::oge, aynı crate içindeki nerede yazılırsa yazılsın hep ornek modülünün içindeki oge adına işaret eder.

:: öneki eskiden ya crate kökünü ya da harici bir crate’i gösteriyordu; artık hiçbir belirsizlik olmadan harici bir crate’i gösterir. Örneğin ::harici::oge her zaman harici harici crate’inin içindeki oge adına gider.

Harici crate yolları

Önceden, bir modülde harici bir crate’i use ile içe aktarmadan kullanmak istiyorsanız yolun başına :: koymanız gerekiyordu.

// Rust 2015

extern crate chrono;

fn islem() {
    // bu kullanım crate kökünde çalışır
    let x = chrono::Utc::now();
}

mod alt_modul {
    fn fonksiyon() {
        // ama alt modülde, `use` ile içe aktarılmadıysa başında `::` gerekir
        let x = ::chrono::Utc::now();
    }
}

Artık harici crate adları, alt modüller dahil tüm crate kapsamındadır.

// Rust 2018

fn islem() {
    // bu kullanım crate kökünde çalışır
    let x = chrono::Utc::now();
}

mod alt_modul {
    fn fonksiyon() {
        // crate'lere alt modüllerde bile doğrudan başvurulabilir
        let x = chrono::Utc::now();
    }
}

Eğer yerel bir modülünüzün ya da öğenizin adı harici bir crate ile aynıysa, o adla başlayan yol yerel modülü ya da öğeyi gösterir. Harici crate’e açıkça başvurmak için ::ad biçimini kullanın.

Artık mod.rs yok

Rust 2015’te bir alt modülünüz varsa:

// Bu `mod` bildirimi `ornek` modülünü
// `ornek.rs` veya `ornek/mod.rs` içinde arar.
mod ornek;

Bu modül ornek.rs ya da ornek/mod.rs içinde bulunabilir. Kendi alt modülleri varsa dosyanın mutlaka ornek/mod.rs olması gerekir. Dolayısıyla ornek modülünün içindeki oge alt modülü ornek/oge.rs yolunda olurdu.

Rust 2018’de, alt modülleri olan bir modülün adının mod.rs olmak zorunda olması kısıtı kaldırıldı. ornek.rs dosyası olduğu gibi ornek.rs kalabilir; alt modül de yine ornek/oge.rs olur. Böylece özel bir dosya adı kullanmak zorunlu olmaktan çıkar. Düzenleyicide çok sayıda dosya açıkken de hepsinin adı net biçimde görünür; ekranda üst üste mod.rs sekmeleri birikmez.

Rust 2015 Rust 2018
.
├── lib.rs
└── ornek/
    ├── mod.rs
    └── oge.rs
.
├── lib.rs
├── ornek.rs
└── ornek/
    └── oge.rs

use yolları

Minimum Rust version: 1.32

Rust 2018, Rust 2015’e göre yol kullanımını sadeleştirir ve birleştirir. Rust 2015’te yollar use bildirimlerinde başka, diğer yerlerde başka türlü çalışıyordu. Özellikle use bildirimlerindeki yollar her zaman crate kökünden başlarken, diğer kodlarda yollar örtük olarak mevcut kapsamdan başlıyordu. Bu farklar üst düzey modülde etkisini pek göstermediği için, her şey ancak alt modülleri olan yeterince büyük bir projede kafa karıştırmaya başlıyordu.

Rust 2018’de ise use bildirimlerindeki yollarla diğer kodlardaki yollar hem üst düzey modülde hem de alt modüllerde aynı şekilde çalışır. Mevcut kapsamdan başlayan göreceli bir yol, harici crate adıyla başlayan bir yol ya da ::, crate, super veya self ile başlayan bir yol kullanabilirsiniz.

Şöyle görünen kod:

// Rust 2015

extern crate futures;

use futures::Future;

mod ornek {
    pub struct Oge;
}

use ornek::Oge;

fn benim_yoklamam() -> futures::Poll { ... }

enum BirEnum {
    V1(usize),
    V2(String),
}

fn islev() {
    let bes = std::sync::Arc::new(5);
    use BirEnum::*;
    match ... {
        V1(i) => { ... }
        V2(s) => { ... }
    }
}

Rust 2018’de de neredeyse aynı görünür; yalnızca extern crate satırını silebilirsiniz:

// Rust 2018

use futures::Future;

mod ornek {
    pub struct Oge;
}

use ornek::Oge;

fn benim_yoklamam() -> futures::Poll { ... }

enum BirEnum {
    V1(usize),
    V2(String),
}

fn islev() {
    let bes = std::sync::Arc::new(5);
    use BirEnum::*;
    match ... {
        V1(i) => { ... }
        V2(s) => { ... }
    }
}

Aynı kod, bir alt modül içinde de hiç değiştirilmeden çalışır:

// Rust 2018

mod alt_modul {
    use futures::Future;

    mod ornek {
        pub struct Oge;
    }

    use ornek::Oge;

    fn benim_yoklamam() -> futures::Poll { ... }

    enum BirEnum {
        V1(usize),
        V2(String),
    }

    fn islev() {
        let bes = std::sync::Arc::new(5);
        use BirEnum::*;
        match ... {
            V1(i) => { ... }
            V2(s) => { ... }
        }
    }
}

Bu yapı, proje içinde kodu bir yerden başka bir yere taşımayı kolaylaştırır ve çok modüllü projelere gereksiz ek karmaşıklık getirilmesini önler.

Anonim trait fonksiyon parametreleri kullanımdan kaldırıldı

Minimum Rust version: 1.31

Özet

Ayrıntılar

RFC #1685 doğrultusunda, trait metod bildirimlerindeki parametrelerin artık isimsiz olmasına izin verilmiyor.

Örneğin 2015 sürümünde şu kullanım geçerliydi:

#![allow(unused)]
fn main() {
trait OrnekTrait {
    fn islem(&self, u8);
}
}

2018 sürümünde ise tüm parametrelere bir argüman adı verilmelidir; bu ad yalnızca _ olsa bile:

#![allow(unused)]
fn main() {
trait OrnekTrait {
    fn islem(&self, deger: u8);
}
}

Yeni Anahtar Kelimeler

Minimum Rust version: 1.27

Özet

Gerekçe

Trait nesneleri için dyn Trait

dyn Trait özelliği, trait nesnelerini kullanmanın yeni sözdizimidir. Kısaca:

  • Box<Trait>, Box<dyn Trait> olur
  • &Trait ve &mut Trait, &dyn Trait ile &mut dyn Trait olur

Ve benzeri dönüşümler yapılır. Kod üzerinde şöyle görünür:

#![allow(unused)]
fn main() {
trait Trait {}

impl Trait for i32 {}

// eski
fn birinci_fonksiyon() -> Box<Trait> {
unimplemented!()
}

// yeni
fn ikinci_fonksiyon() -> Box<dyn Trait> {
unimplemented!()
}
}

Hepsi bu kadar.

Neden?

Trait nesneleri için yalnızca trait adını kullanmak iyi bir karar olmadı. Mevcut sözdizimi, deneyimli kullanıcılar için bile çoğu zaman belirsiz ve kafa karıştırıcıydı. Üstelik bu yaklaşım, alternatiflerinden daha sık kullanılmayan; bazen daha yavaş çalışan ve çoğu durumda alternatifleri kullanılabiliyorken kendisi hiç kullanılamayan bir özelliği öne çıkarıyordu.

Üstelik impl Trait geldikten sonra “impl Trait ile dyn Trait” karşılaştırması, “impl Trait ile Trait” karşılaştırmasına göre çok daha simetrik ve bu yüzden daha anlaşılır hale geldi. impl Trait burada açıklanıyor.

Bu nedenle yeni sürümde, trait nesnesine ihtiyaç duyduğunuz yerlerde yalnızca Trait yazmak yerine dyn Trait kullanmanız gerekir.

async ve await

Bu anahtar kelimeler, Rust’ın async-await özelliğini hayata geçirmek için ayrıldı. Söz konusu özellik daha sonra 1.39.0 kararlı sürümünde yayımlandı.

try anahtar kelimesi

try anahtar kelimesi, try bloklarında kullanılmak üzere ayrılmıştır. Bu bloklar, bu metin yazıldığı sırada henüz kararlı hale getirilmemişti (takip konusu).

Çıkarım Değişkenlerine Yönelik Ham İşaretçi Metod Gönderimi

Özet

Ayrıntılar

Ayrıntılar için Rust konusu #46906’ya bakın.

Cargo Değişiklikleri

Özet

  • Bir Cargo.toml manifest dosyasında hedef tanımı varsa, bu artık diğer hedeflerin otomatik keşfini kendiliğinden devre dışı bırakmaz.
  • path alanı ayarlanmamış hedefler için src/{target_name}.rs biçimindeki hedef yolları artık otomatik olarak çıkarsanmaz.
  • Geçerli dizin için doğrudan cargo install kullanımına artık izin yoktur; mevcut paketi kurmak için cargo install --path . yazmanız gerekir.

Rust 2021

Bilgi
RFC#3085
Yayımlanan sürüm1.56.0

Rust 2021 sürümü, dile yeni yetenekler kazandıran ve daha fazla tutarlılık sağlayan çeşitli değişiklikler içerir. Ayrıca gelecekte yapılacak genişlemeler için de alan açar. Aşağıdaki bölümlerde her değişikliğin ayrıntılarına ineceğiz ve mevcut kodunuzu nasıl taşıyacağınıza dair yönlendirmeler bulacaksınız.

Prelude’e Eklenenler

Özet

  • TryInto, TryFrom ve FromIterator trait’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? self alan doğal metodlar, trait metodlarından önce geldiği için TryInto::try_into çağrısına göre öncelik kazanır. Ancak &self ya da &mut self alan doğal metodlar, otomatik referans gerektirdikleri için aynı önceliği kazanmaz; TryInto::try_into ise self aldığı için buna ihtiyaç duymaz.
  • Bu metodun kaynağı core ya da std mi? 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 self türü dyn Trait mi? Böyleyse, trait içe aktarmaları çözümlemeyi etkilemez ve taşıma lint’inin devreye girmesi gerekmez.

Varsayılan Cargo Özellik Çözücüsü

Özet

  • Cargo.toml içindeki edition = "2021" ayarı, resolver = "2" anlamına gelir.

Ayrıntılar

Rust 1.51.0’dan beri Cargo, Cargo.toml içinde resolver = "2" ile etkinleştirilen yeni bir özellik çözücüsünü isteğe bağlı olarak destekliyor.

Rust 2021 ile birlikte bu varsayılan hale geldi. Yani Cargo.toml dosyasına edition = "2021" yazmak, resolver = "2" demek olur.

Çözücü, bir çalışma alanı için geçerli olan genel bir ayardır ve bağımlılıkların içinde yazıldığında dikkate alınmaz. Bu ayar yalnızca çalışma alanının en üst düzey paketinde geçerlidir. Eğer bir sanal çalışma alanı kullanıyorsanız, yeni çözücüyü etkinleştirmek için [workspace] tanımında [resolver alanını] ayrıca açıkça ayarlamanız gerekir.

Yeni özellik çözücüsü, birden fazla yolla bağımlı olunan crate’ler için istenen tüm özellikleri artık tek bir yerde birleştirmez. Ayrıntılar için Rust 1.51 duyurusuna bakabilirsiniz.

Taşıma

Yeni çözücüye geçiş için otomatik bir taşıma aracı yoktur. Çoğu projede bu güncellemenin etkisi ya çok az olur ya da hiç olmaz.

cargo fix --edition ile güncelleme yaparken, yeni çözücü bağımlılıkları farklı özelliklerle derleyecekse Cargo bir rapor gösterir. Bu rapor şu şekilde görünebilir:

not: Edition 2021’e geçmek, Cargo içinde 2. sürüm özellik çözücüsünün kullanımını etkinleştirir. Bu durum, bazı bağımlılıkların eskisine göre daha az özellikle derlenmesine yol açabilir. Çözücü değişiklikleri hakkında daha fazla bilgi için: https://doc.rust-lang.org/nightly/edition-guide/rust-2021/default-cargo-resolver.html
Aşağıdaki bağımlılıklar derlenirken, belirtilen özellikler artık kullanılmayacaktır:

  bstr v0.2.16: default, lazy_static, regex-automata, unicode
  libz-sys v1.1.3 (as host dependency): libc

Bu rapor, bazı bağımlılıkların artık belirtilen özelliklerle derlenmeyeceğini bildirir.

Derleme hataları

Bazı durumlarda bu değişiklikten sonra projeniz doğru şekilde derlenmeyebilir. Bir paketteki bağımlılık bildirimi, başka bir yerde bazı özelliklerin etkin olduğunu varsayıyorsa ve bu özellikler artık kapalıysa, derleme başarısız olabilir.

Örneğin şöyle bir bağımlılığımız olduğunu düşünelim:

# Cargo.toml

[dependencies]
bstr = { version = "0.2.16", default-features = false }
# ...

Bağımlılık ağacımızın başka bir yerinde de başka bir paket şuna sahip olsun:

# Another package's Cargo.toml

[build-dependencies]
bstr = "0.2.16"

Biz de paketimizde bstr içindeki words_with_breaks metodunu kullanıyor olalım. Bu metodun çalışması için bstr crate’inin unicode özelliğinin etkin olması gerekir. Tarihsel olarak bu çalışıyordu; çünkü Cargo iki paket arasında bstr özelliklerini birleştiriyordu. Ancak Rust 2021’e geçtikten sonra yeni çözücü bstryi iki kez derler: biri varsayılan özelliklerle build-dependency olarak, diğeri ise normal bağımlılığımız olarak özelliksiz biçimde. Böylece bstr, unicode özelliği olmadan derlenmiş olur ve words_with_breaks metodu ortada olmadığı için derleme eksik metod hatasıyla başarısız olur.

Buradaki çözüm, bağımlılığı gerçekten kullandığınız özelliklerle birlikte bildirdiğinizden emin olmaktır. Örneğin:

[dependencies]
bstr = { version = "0.2.16", default-features = false, features = ["unicode"] }

Bazı durumlarda sorun, doğrudan kontrolünüzün olmadığı bir üçüncü taraf bağımlılıktan kaynaklanabilir. Böyle bir durumda, sorunlu bağımlılık için doğru özellik kümesini bildirmesi amacıyla ilgili projeye bir yama göndermeyi düşünebilirsiniz. Alternatif olarak, kendi Cargo.toml dosyanızdan da herhangi bir bağımlılığa özellik ekleyebilirsiniz. Örneğin yukarıdaki bstr bildirimi üçüncü taraf bir bağımlılıkta yer alıyorsa, doğru bağımlılık bildirimini kendi projenize kopyalamanız yeterlidir. Özellikler, yeni çözücünün birleştirme kurallarıyla uyumlu oldukları sürece birleştirilir. Bu kurallar şunlardır:

  • O anda derlenmeyen hedefler için platforma özgü bağımlılıklarda etkin olan özellikler yok sayılır.
  • Build-dependencies ve proc-macro’lar, normal bağımlılıklarla özellik paylaşmaz.
  • Dev-dependencies, onlara ihtiyaç duyan bir hedef derlenmedikçe özellik etkinleştirmez; örneğin testler ya da örnekler gibi.

Gerçek dünyadan bir örnek olarak diesel ile diesel_migrations kullanımını düşünebiliriz. Bu paketler veritabanı desteği sağlar ve hangi veritabanının kullanılacağı bir özellik ile seçilir:

[dependencies]
diesel = { version = "1.4.7", features = ["postgres"] }
diesel_migrations = "1.4.0"

Sorun şu ki diesel_migrations içinde, kendisi de diesele bağlı olan bir iç proc-macro bulunur. Bu proc-macro, kendi diesel kopyasında bağımlılık ağacının geri kalanıyla aynı özelliklerin etkin olduğunu varsayar. Yeni çözücüye geçtikten sonra bu varsayım bozulur; çünkü artık iki ayrı diesel kopyası vardır ve proc-macro için derlenen kopyada postgres özelliği eksiktir.

Buradaki çözüm, dieseli gerekli özelliklerle bir build-dependency olarak eklemektir. Örneğin:

[build-dependencies]
diesel = { version = "1.4.7", features = ["postgres"] }

Bu sayede Cargo, host bağımlılıkları için postgres özelliğini de ekler; yani proc-macro’lar ve build-dependencies tarafında da bu özellik etkin olur. Böylece diesel_migrations proc-macro’su postgres özelliğini alır ve doğru şekilde derlenir.

dieselin 2.0 sürümünde, bu bağımlılık gereksinimi ortadan kaldırılacak şekilde yeniden yapılandırma yapıldığı için bu sorun bulunmaz.

Özellikleri incelemek

cargo tree komutu, yeni çözücüye geçişi kolaylaştırmak için önemli ölçüde iyileştirildi. cargo tree, bağımlılık ağacını incelemek ve hangi özelliklerin etkin olduğunu, daha da önemlisi neden etkin olduğunu görmek için kullanılabilir.

Bunun bir yolu --duplicates bayrağını kullanmaktır; kısa hali -ddir. Bu bayrak size bir paketin birden fazla kez derlendiğini gösterir. Az önceki bstr örneğinde şöyle bir çıktı görebiliriz:

> cargo tree -d
bstr v0.2.16
└── foo v0.1.0 (/MyProjects/foo)

bstr v0.2.16
[build-dependencies]
└── bar v0.1.0
    └── foo v0.1.0 (/MyProjects/foo)

Bu çıktı, bstrnin iki kez derlendiğini ve her iki durumda da bu bağımlılığın hangi zincir üzerinden geldiğini gösterir.

Her paketin hangi özellikleri kullandığını -f bayrağıyla da yazdırabilirsiniz:

cargo tree -f '{p} {f}'

Bu, Cargo’ya çıktının “biçimini” değiştirmesini söyler; böylece hem paketi hem de etkin özellikleri yazdırır.

Hangi “kenarların” gösterileceğini belirtmek için -e bayrağını da kullanabilirsiniz. Örneğin cargo tree -e features, her bağımlılığın araya hangi özellikleri eklediğini gösterir. Bu seçenek, ağacı “ters çevirmek” için kullanılan -i bayrağıyla birlikte daha da yararlı hale gelir. Böylece özelliklerin belirli bir bağımlılığa nasıl aktığını görebilirsiniz. Örneğin bağımlılık ağacı büyükse ve bstrye tam olarak kimin bağımlı olduğunu bilmiyorsanız, aşağıdaki komut bunu gösterir:

> cargo tree -e features -i bstr
bstr v0.2.16
├── bstr feature "default"
│   [build-dependencies]
│   └── bar v0.1.0
│       └── bar feature "default"
│           └── foo v0.1.0 (/MyProjects/foo)
├── bstr feature "lazy_static"
│   └── bstr feature "unicode"
│       └── bstr feature "default" (*)
├── bstr feature "regex-automata"
│   └── bstr feature "unicode" (*)
├── bstr feature "std"
│   └── bstr feature "default" (*)
└── bstr feature "unicode" (*)

Bu çıktı parçası, foo projesinin bara default özelliğiyle bağımlı olduğunu gösterir. Ardından bar, bstrye build-dependency olarak yine default özelliğiyle bağımlıdır. Buradan ayrıca bstrnin default özelliğinin, başka özelliklerin yanında unicode özelliğini de etkinleştirdiğini görebiliriz.

Diziler İçin IntoIterator

Özet

  • Diziler tüm sürümlerde IntoIterator uygular.
  • Metod çağrısı sözdizimi kullanıldığında (array.into_iter() gibi), IntoIterator::into_iter çağrıları Rust 2015 ve Rust 2018’de gizlenir. Bu yüzden array.into_iter(), eskiden olduğu gibi hâlâ (&array).into_iter() olarak çözülür.
  • array.into_iter(), Rust 2021’de doğrudan IntoIterator::into_iter çağrısı anlamına gelir.

Ayrıntılar

Rust 1.53’e kadar yalnızca dizilere verilen referanslar IntoIterator uyguluyordu. Yani &[1, 2, 3] ve &mut [1, 2, 3] üzerinde dönebilirdiniz, ama doğrudan [1, 2, 3] üzerinde dönemezdiniz.

for &oge in &[1, 2, 3] {} // Tamam :)

for oge in [1, 2, 3] {} // Hata :(

Bu, uzun süredir açık olan bir sorundu; ancak çözüm göründüğü kadar basit değildi. Yalnızca trait uygulamasını eklemek, mevcut kodu bozardı. array.into_iter() bugün zaten derlenebiliyor; çünkü metod çağrısı sözdiziminin çalışma biçimi nedeniyle örtük olarak (&array).into_iter() çağrılıyor. Trait uygulamasını eklemek bu anlamı değiştirirdi.

Normalde bu tür kırılmalar, yani bir trait uygulaması eklemek, “küçük” sayılır ve kabul edilebilir görülür. Ancak bu durumda bozulacak kod miktarı çok fazlaydı.

Birçok kez “yalnızca Rust 2021’de diziler için IntoIterator uygulasak” önerisi yapıldı. Ancak bu mümkün değildir. Sürümler karıştırılabildiği için, bir trait uygulamasının bir sürümde var olup diğerinde yok olması sağlanamaz.

Bunun yerine trait uygulaması tüm sürümlere eklendi; başlangıç noktası Rust 1.53.0 oldu. Ancak Rust 2021’e kadar kırılmayı önlemek için küçük bir geçici çözüm kullanıldı. Rust 2015 ve 2018 kodunda derleyici, array.into_iter() ifadesini, sanki bu trait uygulaması yokmuş gibi yine (&array).into_iter() olarak çözer. Bu davranış yalnızca .into_iter() metod çağrısı sözdizimi için geçerlidir. for e in [1, 2, 3], iter.zip([1, 2, 3]) veya IntoIterator::into_iter([1, 2, 3]) gibi diğer sözdizimlerini etkilemez. Bunlar tüm sürümlerde çalışmaya başlar.

Kırılmayı önlemek için böyle küçük bir geçici çözüme ihtiyaç duyulması ideal olmasa da, bu yaklaşım sürümler arasındaki farkı olabilecek en düşük seviyede tutar.

Taşıma

array_into_iter lint’i, Rust 2021’de anlamı değişecek bir into_iter() çağrısı olduğunda tetiklenir. Bu lint, 1.41 sürümünden beri tüm sürümlerde varsayılan uyarıdır; 1.55’te de çeşitli iyileştirmeler aldı. Kodunuz zaten uyarısızsa, büyük olasılıkla Rust 2021’e hazırdır.

Kodunuzu otomatik olarak Rust 2021 ile uyumlu hale getirmek veya zaten uyumlu olduğunu doğrulamak için şunu çalıştırabilirsiniz:

cargo fix --edition

Sürümler arasındaki fark küçük olduğu için Rust 2021’e taşıma da oldukça düzdür.

Diziler üzerindeki into_iter metod çağrılarında öğeler, referans olmaktan çıkıp sahip olunan değerlere dönüşür.

Örneğin:

fn main() {
  let dizi = [1u8, 2, 3];
  for oge in dizi.into_iter() {
    // oge, Rust 2015 ve Rust 2018'de `&u8` olur
    // oge, Rust 2021'de `u8` olur
  }
}

Rust 2021’e taşınırken önceki sürümlerdeki davranışı aynen korumanın en açık yolu, sahip olunan diziler üzerinde de referansla dolaşan iter() metodunu kullanmaktır:

fn main() {
  let dizi = [1u8, 2, 3];
  for oge in dizi.iter() { // <- Bu satır değişti
    // oge, tüm sürümlerde `&u8` olur
  }
}

İsteğe bağlı taşıma

Önceki bir sürümde tam nitelikli metod sözdizimi kullanıyorsanız (IntoIterator::into_iter(array) gibi), bunu metod çağrısı sözdizimine (array.into_iter()) dönüştürebilirsiniz.

Kapanışlarda Ayrık Yakalama

Özet

  • || a.x + 1, artık a yerine yalnızca a.x değerini yakalar.
  • Bu durum, değerlerin farklı zamanlarda düşmesine neden olabilir ya da kapanışların Send veya Clone gibi trait’leri uygulayıp uygulamadığını etkileyebilir.
    • Olası değişiklikler tespit edilirse cargo fix, kapanışın değişkenin tamamını yakalamasını zorlamak için let _ = &a benzeri ifadeler ekler.

Ayrıntılar

Kapanışlar, gövdeleri içinde başvurduğunuz her şeyi otomatik olarak yakalar. Örneğin || a + 1, çevresindeki kapsamdan aya ait bir referansı otomatik olarak yakalar.

Rust 2018 ve öncesinde kapanışlar, yalnızca bir alanı kullansalar bile tüm değişkeni yakalardı. Örneğin || a.x + 1, yalnızca a.xi değil anın tamamına ait bir referansı yakalar. anın bütünüyle yakalanması, diğer alanlarda değişiklik yapmayı ya da taşıma işlemlerini engeller. Bu yüzden aşağıdaki kod derlenmez:

let a = BirYapi::new();
drop(a.x); // Yapının bir alanını dışarı taşır
println!("{}", a.y); // Tamam: Yapının başka bir alanı hâlâ kullanılıyor
let c = || println!("{}", a.y); // Hata: `a`nın tamamını yakalamaya çalışır
c();

Rust 2021 ile birlikte kapanış yakalamaları daha hassas hale geldi. Genellikle yalnızca gerçekten kullanılan alanlar yakalanır. Bazı özel durumlarda bundan fazlası da yakalanabilir; tüm ayrıntılar için Rust referansına bakabilirsiniz. Bu yüzden yukarıdaki örnek Rust 2021’de sorunsuz derlenir.

Ayrık yakalama, RFC 2229 kapsamında önerildi; gerekçeye dair ayrıntılar bu RFC’de yer alır.

Taşıma

2021 sürümünün bir parçası olarak, Rust 2018 kod tabanlarını Rust 2021’e otomatik taşımaya yardımcı olmak için rust_2021_incompatible_closure_captures lint’i eklendi.

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

cargo fix --edition

Aşağıda, otomatik taşıma başarısız olursa ya da taşımanın nasıl çalıştığını daha iyi anlamak isterseniz, Rust 2021 ile uyumlu kapanış yakalamalarına elle nasıl geçileceği anlatılıyor.

Bir kapanışın yakaladığı değişkenleri değiştirmek, programların davranışını iki durumda değiştirebilir ya da derlemeyi durdurabilir:

  • düşme sırasının, yani yıkıcıların ne zaman çalıştığının değişmesi (ayrıntılar);
  • kapanışın hangi trait’leri uyguladığının değişmesi (ayrıntılar).

Aşağıdaki durumlardan biri tespit edilirse cargo fix, kapanışın değişkenin tamamını yakalamasını zorlamak için kapanışın içine bir “yer tutucu let” ekler:

#![allow(unused)]
fn main() {
let x = (vec![22], vec![23]);
let c = move || {
    // `x`in tamamını yakalamayı zorlayan yer tutucu `let`
    let _ = &x;

    // Aksi halde burada yalnızca `x.0` yakalanırdı
    println!("{:?}", x.0);
};
}

Bu korumacı bir analizdir; birçok durumda bu yer tutucu let ifadeleri güvenle kaldırılabilir ve programınız düzgün çalışmaya devam eder.

Joker Desenler

Kapanışlar artık yalnızca okunması gereken verileri yakalar. Bu nedenle aşağıdaki kapanışlar x değişkenini yakalamaz:

#![allow(unused)]
fn main() {
let x = 10;
let c = || {
    let _ = x; // etkisiz işlem
};

let c = || match x {
    _ => println!("Merhaba Dunya!")
};
}

Buradaki let _ = x ifadesi etkisiz bir işlemdir; çünkü _ deseni sağ taraftaki ifadeyi tamamen yok sayar ve x bellekteki bir konuma, yani burada bir değişkene referans verir.

Bu değişiklik tek başına, yani daha az değer yakalanması, öneri üretmez; ancak aşağıdaki “düşme sırası” değişikliğiyle birlikte öneri oluşabilir.

İnce nokta: Benzer görünen bazı ifadeler, örneğin eklediğimiz let _ = &x türündeki yer tutucu letler, etkisiz işlem değildir. Bunun nedeni, sağ taraftaki ifadenin (&x) bellekteki bir konuma verilen çıplak bir referans olmaması; önce değerlendirilmesi gereken, ardından sonucu atılan bir ifade olmasıdır.

Düşme Sırası

Bir kapanış, t değişkeninden bir değerin sahipliğini aldığında, o değer artık t kapsam dışına çıktığında değil, kapanış düştüğünde düşer:

#![allow(unused)]
fn main() {
fn move_value<T>(_: T){}
{
    let t = (vec![0], vec![0]);

    {
        let c = || move_value(t); // `t` burada taşınır
    } // `c` düşer ve beraberinde demet `t` de düşer
} // `t` burada kapsam dışına çıkar
}

Yukarıdaki kod hem Rust 2018’de hem de Rust 2021’de aynı şekilde çalışır. Ancak kapanış yalnızca bir değişkenin bir kısmının sahipliğini alıyorsa, fark oluşabilir:

#![allow(unused)]
fn main() {
fn move_value<T>(_: T){}
{
    let t = (vec![0], vec![0]);

    {
        let c = || {
            // Rust 2018'de `t`nin tamamını yakalar.
            // Rust 2021'de yalnızca `t.0` yakalanır
            move_value(t.0);
        };

        // Rust 2018'de bu bloktan çıkarken hem `c` hem de `t` düşer.
        //
        // Rust 2021'de ise bloktan çıkarken `c` ve `t.0` düşer.
    }

// Rust 2018'de `t` içindeki değer taşınmıştır
// ve artık düşmez.
//
// Rust 2021'de `t.0` içindeki değer taşınmıştır; ama `t.1`
// yerinde kaldığı için burada düşer.
}
}

Çoğu durumda değerlerin farklı zamanlarda düşmesi yalnızca belleğin ne zaman serbest bırakılacağını etkiler ve önemli değildir. Ancak bazı Drop uygulamalarının, yani yıkıcıların yan etkileri vardır. Bu durumlarda düşme sırasını değiştirmek programınızın anlamını da değiştirebilir. Böyle bir senaryoda derleyici, tüm değişkenin yakalanmasını zorlamak için yer tutucu bir let eklenmesini önerir.

Trait Uygulamaları

Kapanışlar, yakaladıkları değerlere bağlı olarak aşağıdaki trait’leri otomatik olarak uygular:

Rust 2021’de farklı değerler yakalandığı için, bir kapanışın hangi trait’leri uyguladığı da değişebilir. Taşıma lint’leri her kapanışı sınar; önce belirli bir trait’i uygulayıp uygulamadığına ve şimdi hâlâ uygulayıp uygulamadığına bakar. Önceden uygulanan ama artık uygulanmayan bir trait bulurlarsa yer tutucu let ifadeleri eklenir.

Örneğin ham işaretçileri iş parçacıkları arasında taşımak için sık kullanılan yöntemlerden biri, bunları bir yapı içinde sarmalamak ve ardından sarmalayıcı için Send/Sync otomatik trait’lerini uygulamaktır. thread::spawn içine verilen kapanış, sarmalayıcının yalnızca belirli alanlarını kullansa da yine de tüm sarmalayıcı yakalanır. Sarmalayıcı Send/Sync olduğu için kod güvenli sayılır ve başarıyla derlenir.

Ayrık yakalama ile birlikte kapanışta anılan belirli alan yakalanır. Bu alanın kendisi başlangıçta Send/Sync olmadığı için sarmalayıcının sağladığı koruma da etkisiz kalır.

#![allow(unused)]
fn main() {
use std::thread;

struct Isaretci(*mut i32);
unsafe impl Send for Isaretci {}


let mut x = 5;
let px = Isaretci(&mut x as *mut i32);

let c = thread::spawn(move || {
    unsafe {
        *(px.0) += 10;
    }
}); // Kapanış, `Send` olmayan `px.0` alanını yakaladı
}

Panic Makrosunda Tutarlılık

Özet

  • panic!(..) artık println!() gibi her zaman format_args!(..) kullanır.
  • { karakterini {{ diye kaçırmadan panic!("{") yazmak artık kabul edilmez.
  • x bir dizgi sabiti değilse panic!(x) artık kabul edilmez.
    • Dizgi olmayan bir yükle panic üretmek için std::panic::panic_any(x) kullanın.
    • Ya da xin Display uygulamasını kullanmak için panic!("{}", x) yazın.
  • Aynı kural assert!(expr, ..) için de geçerlidir.

Ayrıntılar

panic!() makrosu, Rust’ın en iyi bilinen makrolarından biridir. Ancak bazı ince sürprizleri vardır ve geriye dönük uyumluluk nedeniyle bunları öylece değiştiremeyiz.

// Rust 2018
panic!("{}", 1); // Tamam, "1" ile panic üretir
panic!("{}"); // Tamam, "{}" ile panic üretir

panic!() makrosu yalnızca birden fazla argümanla çağrıldığında dizgi biçimlendirmesi kullanır. Tek argümanla çağrıldığında ise o argümana bile bakmaz.

// Rust 2018
let a = "{";
println!(a); // Hata: İlk argüman bir biçim dizgisi sabiti olmalı
panic!(a); // Tamam: panic makrosu bunu umursamaz

Hatta panic!(123) gibi dizgi olmayan değerleri bile kabul eder. Bu kullanım pek yaygın ya da yararlı değildir; çünkü şaşırtıcı derecede faydasız bir mesaj üretir: panicked at 'Box<Any>'.

Bu, özellikle örtük biçim argümanları kararlı hale geldiğinde sorun olacaktır. Bu özellik println!("hello {name}") ifadesini println!("hello {}", name) için bir kısayola dönüştürür. Ancak panic!("hello {name}") beklendiği gibi çalışmaz; çünkü panic!() tek argümanı biçim dizgisi olarak işlemez.

Bu kafa karıştırıcı durumu önlemek için Rust 2021 daha tutarlı bir panic!() makrosu getirir. Yeni panic!() makrosu artık tek argüman olarak rastgele ifadeleri kabul etmez. println!() gibi ilk argümanı her zaman biçim dizgisi olarak işler. panic!() artık keyfi yükleri kabul etmeyeceği için, panic_any(), biçimlenmiş dizgi dışındaki bir değerle panic üretmenin tek yolu olur.

// Rust 2021
panic!("{}", 1); // Tamam, "1" ile panic üretir
panic!("{}"); // Hata, eksik argüman
panic!(a); // Hata, bir dizgi sabiti olmalı

Ayrıca Rust 2021’de core::panic!() ile std::panic!() birebir aynı olur. Bugün bu ikisi arasında tarihsel farklar vardır ve #![no_std] açılıp kapatılırken bu farklar fark edilebilir.

Taşıma

non_fmt_panics lint’i, Rust 2021’de hataya dönüşecek kullanımdan kaldırılmış bir panic çağrısı bulunduğunda tetiklenir. Bu lint, 1.50 sürümünden beri tüm sürümlerde varsayılan uyarıdır; sonraki sürümlerde de birkaç iyileştirme aldı. Kodunuz zaten uyarısızsa, büyük olasılıkla Rust 2021’e hazırdır.

Kodunuzu otomatik olarak Rust 2021 ile uyumlu hale getirmek ya da zaten uyumlu olduğunu doğrulamak için şunu çalıştırabilirsiniz:

cargo fix --edition

Elle taşıma yapmayı seçerseniz ya da buna ihtiyaç duyarsanız, tüm panic çağrılarını ya println! ile aynı biçimlendirmeyi kullanacak şekilde ya da dizgi olmayan veriler için std::panic::panic_any kullanacak şekilde güncellemeniz gerekir.

Örneğin panic!(BenimYapi) kullanıyorsanız, bunu std::panic::panic_any kullanımına dönüştürmeniz gerekir. Bunun makro değil fonksiyon olduğuna dikkat edin: std::panic::panic_any(BenimYapi).

Panic mesajında süslü parantezler bulunup argüman sayısı yanlışsa (panic!("Bazi susluler: {}") gibi), dizgi sabitiyle panic üretmek için ya println! ile aynı sözdizimini kullanabilirsiniz: panic!("{}", "Bazi susluler: {}") ya da süslü parantezleri kaçırabilirsiniz: panic!("Bazi susluler: {{}}").

Ayrılmış Söz Dizimi

Özet

  • any_identifier#, any_identifier"...", any_identifier'...' ve 'any_identifier# artık ayrılmış söz dizimidir ve artık ayrı token’lara bölünmez.
  • Bu değişiklik en çok makroları ilgilendirir. Örneğin quote!{ #a#b } artık kabul edilmez.
  • Anahtar kelimelere özel davranılmaz; örneğin match"..." {} da artık kabul edilmez.
  • Hata almamak için tanımlayıcı ile onu izleyen #, " ya da ' arasında boşluk bırakın.
  • Sürüm taşıma araçları bu tür durumlarda boşluk eklemenize yardımcı olur.

Ayrıntılar

Gelecekte yeni sözdizimlerine yer açmak için önekli tanımlayıcılar, sabitler ve ömürler için bazı biçimleri ayırmaya karar verdik: prefix#identifier, prefix"string", prefix'c', prefix#123 ve 'prefix#. Buradaki prefix herhangi bir tanımlayıcı olabilir. Elbette b'...' gibi zaten anlamı olan önekler ve r"..." gibi ham dizgiler bunun dışındadır.

Bu sayede gelecekte, yeni bir sürüm sınırı gerektirmeden üzerine genişleyebileceğimiz bir sözdizimi alanı elde etmiş oluyoruz. Bunu bir sonraki sürüme kadar geçici bir sözdizimi için ya da uygun görülürse kalıcı bir sözdizimi için kullanabiliriz.

Bir sürüm sınırı olmasaydı bu değişiklik kırıcı olurdu; çünkü makrolar şu anda hello"world" gibi sözdizimlerini iki ayrı token olarak, yani hello ve "world" şeklinde kabul edebiliyor. Neyse ki otomatik düzeltme basittir: araya boşluk eklemek yeterlidir, yani hello "world". Aynı şekilde prefix#ident, prefix #ident olmalıdır. Sürüm taşıma araçları bu düzeltmeye yardımcı olur.

Bu biçimlerin artık tokenlaştırma hatası üretmesi dışında, RFC henüz hiçbir öneke özel bir anlam atamaz. Belirli öneklere anlam verilmesi gelecekteki önerilere bırakılmıştır. Bu önekleri şimdiden ayırdığımız için, ileride böyle anlamlar eklemek kırıcı değişiklik sayılmayacaktır.

Gelecekte görebileceğimiz olası bazı yeni önekler şunlardır; gerçi henüz hiçbiri için kesin karar verilmiş değildir:

  • k#keyword: Geçerli sürümde henüz anahtar kelime olmayan sözcükleri yazabilmek için. Örneğin async, 2015 sürümünde anahtar kelime değilken k#async biçimi, async sözcüğünü anahtar kelime olarak ayırmak için 2018 sürümünü beklemeden kullanılabilirdi.

  • f"": biçim dizgisi için bir kısayol. Örneğin f"hello {name}", eşdeğer format!() çağrısının kısa yazımı olabilir.

  • s"": String sabitleri için.

Taşıma

2021 sürümünün bir parçası olarak, Rust 2018 kod tabanlarını Rust 2021’e otomatik taşımaya yardımcı olmak için rust_2021_prefixes_incompatible_syntax lint’i eklendi.

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

cargo fix --edition

Kodunuzu elle taşımak isterseniz ya da buna ihtiyaç duyarsanız süreç oldukça düzdür.

Şöyle tanımlanmış bir makronuz olduğunu düşünelim:

#![allow(unused)]
fn main() {
macro_rules! benim_makrom {
    ($a:tt $b:tt) => {};
}
}

Rust 2015 ve 2018’de, ilk token ağacı ile ikincisi arasına boşluk koymadan bu makroyu şu şekilde çağırmak geçerlidir:

benim_makrom!(z"hey");

Bu z öneki Rust 2021’de artık kabul edilmez. Bu yüzden makroyu çağırmak için önekten sonra şu şekilde bir boşluk eklemeniz gerekir:

benim_makrom!(z "hey");

Ham Ömürler

Özet

  • 'r#ident_or_keyword artık bir ömür olarak kullanılabilir; böylece 'r#fn gibi anahtar kelimeler de ömür adı olarak yazılabilir.

Ayrıntılar

Ham ömürler, yeni anahtar kelimeler getiren daha yeni sürümlere taşınmayı kolaylaştırmak için 2021 sürümünde eklendi. Bu özellik, tanımlayıcılar için aynı işi yapan [ham tanımlayıcılar] ile benzerdir. Örneğin 2024 sürümü gen anahtar kelimesini getirdi. Ömürler anahtar kelime olamayacağı için 'gen ömrünü kullanan kod derlenmez hale gelirdi. Ham ömürler sayesinde taşıma lint’i bu tür ömürleri 'r#gen biçimine dönüştürebilir ve böylece anahtar kelimeler kullanılabilir hale gelir.

2021 öncesi sürümlerde ham ömürler ayrı token’lar olarak ayrıştırılır. Örneğin 'r#foo, üç token şeklinde yorumlanır: 'r, # ve foo.

Taşıma

2021 sürümünün bir parçası olarak, Rust 2018 kod tabanlarını Rust 2021’e otomatik taşımaya yardımcı olmak için rust_2021_prefixes_incompatible_syntax lint’i eklendi.

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

cargo fix --edition

Kodunuzu elle taşımak isterseniz ya da buna ihtiyaç duyarsanız süreç oldukça düzdür.

Şöyle tanımlanmış bir makronuz olduğunu varsayalım:

#![allow(unused)]
fn main() {
macro_rules! benim_makrom {
    ($a:tt $b:tt $c:tt) => {};
}
}

Rust 2015 ve 2018’de, token’lar arasına boşluk koymadan bu makroyu şu şekilde çağırmak geçerlidir:

benim_makrom!('r#foo);

2021 sürümünde bu artık tek bir token olarak ayrıştırılır. Bu yüzden makroyu çağırmak için tanımlayıcıdan önce şu şekilde boşluk eklemeniz gerekir:

benim_makrom!('r# foo);

Hatalara Yükseltilen Uyarılar

Özet

  • bare_trait_objects ve ellipsis_inclusive_range_patterns lint’lerini tetikleyen kod, Rust 2021’de hata verir.

Ayrıntılar

Mevcut iki lint, Rust 2021’de kesin hataya dönüşüyor; ancak eski sürümlerde uyarı olarak kalmaya devam ediyorlar.

bare_trait_objects:

Rust 2021’de trait nesnelerini göstermek için dyn anahtar kelimesini kullanmak zorunlu hale gelir.

Örneğin aşağıdaki kodda &MyTrait içinde dyn anahtar kelimesi olmadığı için, Rust 2021’de bu artık yalnızca lint üretmek yerine doğrudan hata verir:

#![allow(unused)]
fn main() {
pub trait BenimTrait {}

pub fn benim_fonksiyonum(_trait_nesnesi: &BenimTrait) { // `&dyn BenimTrait` olmalı
  unimplemented!()
}
}

ellipsis_inclusive_range_patterns:

Kullanımdan kaldırılmış ... sözdizimi, yani bitiş değerinin aralığa dahil olduğu kapsayıcı aralık desenleri için Rust 2021’de artık kabul edilmez. Bunun yerini, ifadelerle de tutarlı olan ..= almıştır.

Örneğin aşağıdaki kodda desende ... kullanıldığı için, Rust 2021’de bu kullanım uyarı değil hata üretir:

#![allow(unused)]
fn main() {
pub fn yuze_esit_veya_kucuk_mu(sayi: u8) -> bool {
  matches!(sayi, 0...100) // `0..=100` olmalı
}
}

Taşıma

Rust 2015 ya da 2018 kodunuz bare_trait_objects veya ellipsis_inclusive_range_patterns için hiçbir uyarı üretmiyorsa ve siz de bu lint’leri #![allow()] ya da benzeri bir mekanizma ile özellikle izinli hale getirmediyseniz, ayrıca taşıma yapmanız gerekmez.

Desenlerde ... kullanan ya da trait nesnelerinde dyn yazmayan herhangi bir crate’i otomatik taşımak için cargo fix --edition çalıştırabilirsiniz.

macro-rules İçinde Or Desenleri

Özet

  • Desenlerin macro_rules makroları içinde çalışma biçimi biraz değişiyor:
    • macro_rules içindeki $_:pat artık | kullanımını da eşleştirir; örneğin A | B.
    • Yeni $_:pat_param, eskiden $_:pat nasıl davranıyorsa öyle davranır; üst düzey | ile eşleşmez.
    • $_:pat_param tüm sürümlerde kullanılabilir.

Ayrıntılar

Rust 1.53.0’dan itibaren desenler, desenin herhangi bir yerinde iç içe | kullanımını destekleyecek şekilde genişletildi. Böylece Some(1) | Some(2) yerine Some(1 | 2) yazabiliyorsunuz. Bu kullanım daha önce zaten geçerli olmadığı için kırıcı bir değişiklik değildir.

Ancak bu değişiklik macro_rules makrolarını da etkiler. Bu makrolar, :pat parçacık belirteci ile desen kabul edebilir. Şu anda :pat, üst düzey | ile eşleşmez; çünkü Rust 1.53 öncesinde tüm desenler, her iç içe düzeyde | içeremiyordu. A | B gibi desenleri kabul eden matches!() gibi makrolar bu yüzden $($_:pat)|+ benzeri biçimler kullanır.

Bu değişiklik mevcut makroları bozabileceği için :patin anlamı Rust 1.53.0’da | içerecek şekilde değiştirilmedi. Bunun yerine bu değişiklik Rust 2021’de devreye girdi. Yeni sürümde :pat parçacık belirteci A | B ile de eşleşir.

Rust 2021’de $_:pat parçacıklarının ardından açıkça | gelemez. Ancak bazı durumlarda hâlâ ardından | gelen desen parçalarını eşleştirmek istenir. Bu yüzden eski davranışı korumak için :pat_param parçacık belirteci eklendi.

Sürümlerin crate başına uygulandığını unutmamak önemlidir. Dolayısıyla burada önemli olan tek sürüm, makronun tanımlandığı crate’in sürümüdür. Makronun kullanıldığı crate’in sürümü, makronun çalışma biçimini değiştirmez.

Taşıma

rust_2021_incompatible_or_patterns lint’i, Rust 2021’de anlamı değişecek bir $_:pat kullanımı bulunduğunda tetiklenir.

Kodunuzu otomatik olarak Rust 2021 ile uyumlu hale getirmek ya da zaten uyumlu olduğunu doğrulamak için şunu çalıştırabilirsiniz:

cargo fix --edition

Eğer makronuz, $_:patin desenlerde üst düzey | kullanımını eşleştirmemesine güveniyorsa, her $_:pat kullanımını $_:pat_param olarak değiştirmeniz gerekir.

Örneğin:

#![allow(unused)]
fn main() {
macro_rules! benim_makrom { 
	($x:pat | $y:pat) => {
		// YAPILACAK: uygulama
	} 
}

// Bu makro Rust 2018'de çalışır; çünkü `$x:pat`, `|` ile eşleşmez:
benim_makrom!(1 | 2);

// Ancak Rust 2021'de `$_:pat` parçacığı `|` ile eşleşir ve ardından
// bir `|` gelmesine izin verilmez. Bu makronun Rust 2021'de de çalışması için
// aşağıdaki biçime çevirin:
macro_rules! benim_makrom { 
	($x:pat_param | $y:pat) => { // <- bu satır farklı
		// YAPILACAK: uygulama
	} 
}
}

C Dizgisi Sabitleri

Özet

  • c"foo" ya da cr"foo" biçimindeki sabitler, &core::ffi::CStr türünde bir dizgiyi temsil eder.

Ayrıntılar

Rust 1.77 ile birlikte C dizgileri, c veya cr önekiyle C dizgisi sabiti sözdizimi kullanılarak yazılabilir hale geldi.

Daha önce, sonu NUL baytıyla biten C API’leriyle birlikte çalışabilecek geçerli bir dizgi sabiti üretmek zordu. cstr crate’i bunun için yaygın bir çözümdü; ama bu yaklaşım, derlenmesi maliyetli bir proc-macro gerektiriyordu. Artık C dizgileri doğrudan sabit sözdizimiyle yazılabiliyor. Bu sözdizimi, sonuna otomatik olarak NUL baytı eklenmiş &core::ffi::CStr türünde bir değer üretir.

#![allow(unused)]
fn main() {
use core::ffi::CStr;

assert_eq!(c"hello", CStr::from_bytes_with_nul(b"hello\0").unwrap());
assert_eq!(
    c"byte kacislari \xff calisir",
    CStr::from_bytes_with_nul(b"byte kacislari \xff calisir\0").unwrap()
);
assert_eq!(
    c"unicode kacislari \u{00E6} calisir",
    CStr::from_bytes_with_nul(b"unicode kacislari \xc3\xa6 calisir\0").unwrap()
);
assert_eq!(
    c"unicode karakterleri αβγ UTF-8 olarak kodlanir",
    CStr::from_bytes_with_nul(
        b"unicode karakterleri \xce\xb1\xce\xb2\xce\xb3 UTF-8 olarak kodlanir\0"
    )
    .unwrap()
);
assert_eq!(
    c"dizgiler \
        birden fazla satirda surebilir",
    CStr::from_bytes_with_nul(b"dizgiler birden fazla satirda surebilir\0").unwrap()
);
}

C dizgileri, içeride NUL baytı bulunmasına izin vermez; örneğin \0 kaçağı gibi.

Normal dizgilere benzer şekilde, C dizgileri de cr önekiyle “ham” sözdizimini destekler. Bu ham C dizgileri ters eğik çizgi kaçışlarını işlemez; bu da ters eğik çizgi içeren dizgileri yazmayı kolaylaştırır. Çift tırnak karakterleri, tırnakların etrafını # ile sararak içeri alınabilir. İçeride "# dizileri varsa belirsizliği önlemek için birden fazla # de kullanılabilir.

#![allow(unused)]
fn main() {
assert_eq!(cr"foo", c"foo");
// İç tırnakları eklemek için `#` karakteri kullanılabilir.
assert_eq!(cr#""foo""#, c"\"foo\"");
// Bu örnek iki `#` gerektirir.
assert_eq!(cr##""foo"#"##, c"\"foo\"#");
// Kaçış dizileri işlenmez.
assert_eq!(cr"C:\foo", c"C:\\foo");
}

Daha fazla ayrıntı için Referans bölümüne bakın.

Taşıma

Taşıma yalnızca, c"…" ya da cr"…" benzeri bir token dizisini daha önce iki ayrı token olarak varsayan makrolar için gereklidir. 2021 sürümünde bu yapı tek token olarak görünür.

2021 sürümündeki sözdizimi ayırma çalışmasının bir parçası olarak, bu sorunla karşılaşabilecek her makro girdisi rust_2021_prefixes_incompatible_syntax taşıma lint’inden uyarı üretmelidir. Ayrıntılar için ilgili bölüme bakın.

Rust 2024

Bilgi
RFC#3501
Yayımlanan sürüm1.85.0

Dil

Aşağıdaki bölümler, 2024 sürümündeki dile ilişkin değişiklikleri ayrıntılı olarak açıklar.

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.

if let Geçici Kapsamı

Özet

  • if let $pat = $expr { .. } else { .. } ifadesinde, $expr değerlendirilirken oluşan geçici değerler artık else dalına girdikten sonra değil, programa else dalına girmeden önce düşer.

Ayrıntılar

2024 sürümü, bir if let ifadesindeki scrutinee1 içinde bulunan geçici değerlerin düşme kapsamını değiştirir. Amaç, geçicinin gereğinden uzun yaşamasından doğan beklenmedik davranışları azaltmaktır.

2024’ten önce geçiciler if let ifadesinin ötesine uzatılabiliyordu. Örneğin:

#![allow(unused)]
fn main() {
// 2024'ten önce
use std::sync::RwLock;

fn f(value: &RwLock<Option<bool>>) {
    if let Some(x) = *value.read().unwrap() {
        println!("deger {x}");
    } else {
        let mut v = value.write().unwrap();
        if v.is_none() {
            *v = Some(true);
        }
    }
    // <--- 2021'de okuma kilidi burada düşer
}
}

Bu örnekte value.read() çağrısıyla oluşturulan geçici okuma kilidi, if let ifadesi bitene kadar, yani else bloğundan sonrasına kadar düşmez. else bloğu çalışırsa, yazma kilidi almaya çalıştığında kilitlenmeye neden olur.

2024 sürümü, geçicilerin ömrünü then-bloğu tamamen değerlendirildiği ya da program denetiminin else bloğuna geçtiği noktaya kadar kısaltır.

#![allow(unused)]
fn main() {
// 2024 ile birlikte
use std::sync::RwLock;

fn f(value: &RwLock<Option<bool>>) {
    if let Some(x) = *value.read().unwrap() {
        println!("deger {x}");
    }
    // <--- 2024'te okuma kilidi burada düşer
    else {
        let mut v = value.write().unwrap();
        if v.is_none() {
            *v = Some(true);
        }
    }
}
}

Geçici kapsamların nasıl genişletildiğine dair daha fazla bilgi için geçici kapsam kurallarına bakın. Kuyruk ifadelerine yapılan benzer bir değişiklik için kuyruk ifadesi geçici kapsamı bölümüne göz atın.

Taşıma

if let ifadesini match ile yeniden yazmak her zaman güvenlidir. match scrutinee’sinin geçicileri match ifadesinin sonrasına, genelde ifadenin bittiği noktaya kadar uzatılır. Bu, if letin 2021’deki davranışıyla aynıdır.

if_let_rescope lint’i, bu değişiklik yüzünden bir ömür sorunu doğduğunda ya da if let scrutinee’sinden özel ve sıradan olmayan bir Drop yıkıcısına sahip geçici değer üretildiğini tespit ettiğinde bir düzeltme önerir. Örneğin yukarıdaki örnek, cargo fix önerisi kabul edildiğinde şöyle yeniden yazılabilir:

#![allow(unused)]
fn main() {
use std::sync::RwLock;
fn f(value: &RwLock<Option<bool>>) {
    match *value.read().unwrap() {
        Some(x) => {
            println!("deger {x}");
        }
        _ => {
            let mut s = value.write().unwrap();
            if s.is_none() {
                *s = Some(true);
            }
        }
    }
    // <--- Okuma kilidi hem 2021'de hem 2024'te burada düşer
}
}

Bu özel örnekte, az önce bahsedilen kilitlenme yüzünden bu muhtemelen istediğiniz şey değildir. Ancak bazı senaryolar, geçicilerin else dalının ötesinde tutulduğunu varsayıyor olabilir; böyle bir durumda eski davranışı korumak isteyebilirsiniz.

if_let_rescope lint’i, otomatik sürüm taşımasına dahil olan rust-2024-compatibility lint grubunun parçasıdır. Kodunuzu Rust 2024 ile uyumlu hale getirmek için şunu çalıştırın:

cargo fix --edition

Taşıma sonrasında if letten matche çevrilen tüm değişiklikleri gözden geçirmeniz ve geçicilerin ne zaman düşmesi gerektiği açısından hangi davranışa ihtiyaç duyduğunuza karar vermeniz önerilir. Eğer değişikliğin gereksiz olduğuna karar verirseniz dönüşümü yeniden if lete çevirebilirsiniz.

Sürüm taşımasını yapmadan bu uyarıları elle incelemek isterseniz lint’i şu şekilde etkinleştirebilirsiniz:

#![allow(unused)]
fn main() {
// Elle taşıma yapmak için bunu crate köküne ekleyin.
#![warn(if_let_rescope)]
}

  1. scrutinee, if let ifadesinde eşleştirilen ifadedir.

if ve while İçinde let Zincirleri

Özet

  • if ve while koşul işlenenlerinde let ifadelerinin zincirlenmesine izin verilir.

Ayrıntılar

2024 sürümüyle birlikte if ve while koşullarının içinde let ifadelerini zincirlemek mümkün hale geldi. Buradaki zincirleme, && zincirlerini ifade eder. let ifadelerinin yine de üst düzeyde bulunması gerekir; bu yüzden if (let Some(selam) = foo || let Some(selam) = bar) geçerli değildir.

2024’ten önce let, doğrudan if ya da while sözcüğünden sonra gelmek zorundaydı; yani yalnızca if let ya da while let özel biçimleri vardı. Şimdi ise if ve while, bir ya da daha fazla let ifadesinden oluşan zincirlere izin verir; bunlar bool türündeki ifadelerle de karışık olabilir.

#![allow(unused)]
fn main() {
fn ilk_ikiyi_topla(sayilar: &[u8]) -> Option<u8> {
    let mut yineleyici = sayilar.iter();
    if let Some(ilk) = yineleyici.next()
        && let Some(ikinci) = yineleyici.next()
    {
        ilk.checked_add(*ikinci)
    } else {
        None
    }
}
}

Bu özellik, 2024 sürümüne ait [if let yeniden kapsamlandırması] değişikliğini gerektirdiği için sürüm kapısına bağlıdır.

Taşıma

Bu özellik, geçerli Rust programları kümesini gerçekten genişlettiği için 2024 sürümüne geçerken ayrıca bir taşıma gerektirmez.

Kuyruk İfadesi Geçici Kapsamı

Özet

  • Bir [fonksiyon] ya da kapanış gövdesinin veya bir [blok] ifadesinin kuyruk ifadesi değerlendirilirken oluşan geçici değerler artık yerel değişkenlerden önce düşebilir ve bazen bir sonraki daha büyük geçici kapsama uzatılmaz.

Ayrıntılar

2024 sürümü, kuyruk ifadelerdeki geçici değerlerin düşme sırasını değiştirir. 2024’ten önce kuyruk ifadedeki geçicilerin bloktan daha uzun yaşaması ve yerel değişken bağlarından daha sonra düşmesi çoğu zaman şaşırtıcı geliyordu:

#![allow(unused)]
fn main() {
// 2024'ten önce
use std::cell::RefCell;
fn f() -> usize {
    let c = RefCell::new("..");
    c.borrow().len() // error[E0597]: `c` does not live long enough
}
}

Bu kod 2021 sürümünde şu hatayı üretir:

error[E0597]: `c` does not live long enough
 --> src/lib.rs:4:5
  |
3 |     let c = RefCell::new("..");
  |         - binding `c` declared here
4 |     c.borrow().len() // error[E0597]: `c` does not live long enough
  |     ^---------
  |     |
  |     borrowed value does not live long enough
  |     a temporary with access to the borrow is created here ...
5 | }
  | -
  | |
  | `c` dropped here while still borrowed
  | ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `Ref<'_, &str>`
  |
  = note: the temporary is part of an expression at the end of a block;
          consider forcing this temporary to be dropped sooner, before the block's local variables are dropped
help: for example, you could save the expression's value in a new local variable `x` and then make `x` be the expression at the end of the block
  |
4 |     let x = c.borrow().len(); x // error[E0597]: `c` does not live long enough
  |     +++++++                 +++

For more information about this error, try `rustc --explain E0597`.

2021’de yerel c değişkeni, c.borrow() ile oluşturulan geçiciden önce düşer. 2024 sürümü bunu tersine çevirir; önce c.borrow() geçicisi düşer, ardından yerel c değişkeni düşer ve böylece kod beklendiği gibi derlenir.

Geçici kapsam daralabilir

Bir ifadeyi değerlendirmek için geçici bir değer oluşturulduğunda, bu geçici geçici kapsam kurallarına göre düşer. Bu kurallar geçicinin ne kadar süre yaşatılacağını belirler. 2024’ten önce blokların kuyruk ifadelerinden gelen geçiciler, bir sonraki geçici kapsam sınırına kadar blok dışına uzatılırdı. Çoğu zaman bu, ifadenin ya da fonksiyon gövdesinin sonu olurdu. 2024’te ise kuyruk ifadenin geçicileri artık doğrudan blok sonunda, yani blok içindeki yerel değişkenlerden önce düşebilir.

Geçici kapsamın bu şekilde daralması, bazı programların 2024’te derlenememesine yol açabilir. Örneğin:

// Bu örnek 2021'de çalışır, ama 2024'te derlenmez.
fn main() {
    let x = { &String::from("1234") }.len();
}

Bu örnekte 2021’de geçici String, blok dışına ve len() çağrısının ötesine uzatılır, ardından ifadenin sonunda düşer. 2024’te ise blok biter bitmez düşer ve ödünç alınmışken geçicinin düşmesiyle ilgili derleme hatası oluşur.

Bu tür durumlarda çözüm, blok ifadesini yerel bir değişkene taşımaktır; böylece geçici değer yeterince uzun yaşar:

fn main() {
    let s = { &String::from("1234") };
    let x = s.len();
}

Bu özel örnek, geçici ömür uzatımından yararlanır. Geçici ömür uzatımı, geçicilerin normalden daha uzun yaşamasına izin veren belirli kurallar kümesidir. Burada geçici String bir referans arkasında olduğu için bir sonraki ifadenin üzerinde len() çağrısı yapabileceği kadar uzun süre uzatılır.

if let ifadelerinin geçici kapsamlarına yapılan benzer değişiklik için if let geçici kapsamı bölümüne bakın.

Taşıma

Ne yazık ki kuyruk ifadedeki geçici değerlerin ömrünü kısaltırken anlamı birebir koruyan yeniden yazımlar yoktur1. tail_expr_drop_order lint’i, kuyruk ifadede özel ve sıradan olmayan bir Drop yıkıcısına sahip geçici değer oluşturulup oluşturulmadığını tespit eder. cargo fix --edition çalıştırılırken bu lint’ten uyarılar gelir; ancak otomatik değişiklik yapılmaz. Bu yüzden uyarıları elle inceleyip ayarlama gerekip gerekmediğine karar vermeniz önerilir.

Sürüm taşımasını yapmadan bu uyarıları elle incelemek isterseniz lint’i şu şekilde açabilirsiniz:

#![allow(unused)]
fn main() {
// Elle taşıma yapmak için bunu crate köküne ekleyin.
#![warn(tail_expr_drop_order)]
}

  1. Ayrıntılar RFC 3606 içinde belgelenmiştir.

Match Ergonomisi Çekinceleri

Özet

  • Bir bağ üzerinde mut, ref ya da ref mut yazmak artık yalnızca, o bağa kadar gelen desen tamamen açıksa mümkündür; yani match ergonomisi kullanmıyorsa.
    • Başka bir deyişle, varsayılan bağlama modu move değilse bağ üzerinde mut, ref ya da ref mut yazmak hatadır.
  • Referans desenlerine (& ya da &mut) yalnızca bir desenin tamamen açık olan ön kısmında izin verilir.
    • Başka bir deyişle, referans desenleri yalnızca varsayılan bağlama modu move iken scrutinee içindeki referanslarla eşleşebilir.

Ayrıntılar

Arka plan

match, let ve benzeri yapılarda bir desen ile bir scrutinee’yi eşleştiririz. Örneğin:

#![allow(unused)]
fn main() {
let &[&mut [ref x]] = &[&mut [()]]; // x: &()
//  ~~~~~~~~~~~~~~~   ~~~~~~~~~~~~
//      Desen          Scrutinee
}

Böyle bir desene tamamen açık denir; çünkü scrutinee içindeki hiçbir referansı atlamaz. Buna karşılık aşağıdaki, başka açıdan eşdeğer olan desen tamamen açık değildir:

#![allow(unused)]
fn main() {
let [[x]] = &[&mut [()]]; // x: &()
}

Bunun gibi desenler, ilk olarak RFC 2005 ile gelen match ergonomisini kullanıyor sayılır.

Match ergonomisi altında, bir deseni adım adım scrutinee ile eşleştirirken varsayılan bağlama modunu takip ederiz. Bu mod move, ref mut ya da ref olabilir ve başlangıçta movedur. Bir bağa ulaştığımızda açık bir bağlama modu verilmediyse bağın türüne karar vermek için varsayılan mod kullanılır.

Örneğin burada açık bir bağlama modu veriyoruz ve bu yüzden x referansla bağlanıyor:

#![allow(unused)]
fn main() {
let ref x = (); // &()
}

By contrast:

#![allow(unused)]
fn main() {
let [x] = &[()]; // &()
}

Burada desende, scrutinee içindeki dıştaki paylaşımlı referansı geçiyoruz. Bu, varsayılan bağlama modunu movedan refe çevirir. Açık bir bağlama modu belirtilmediği için x bağlanırken ref modu kullanılır.

mut kısıtı

Rust 2021 ve daha eski sürümlerde şu tuhaflığa izin veriliyordu:

#![allow(unused)]
fn main() {
let [x, mut y] = &[(), ()]; // x: &(), mut y: ()
}

Burada desende paylaşımlı referansı geçtiğimiz için varsayılan bağlama modu refe döner. Ama bu sürümlerde bağ üzerine mut yazmak, varsayılan bağlama modunu yeniden movea sıfırlar.

Bu şaşırtıcı olabilir; çünkü değiştirilebilirliğin türü etkilemesi sezgisel değildir.

Bunu düzeltebilmek için alan bırakmak adına, Rust 2024’te varsayılan bağlama modu move değilken bağ üzerine mut yazmak hata oldu. Yani mut, yalnızca desen o bağa kadar tamamen açıksa yazılabilir.

Rust 2024’te yukarıdaki örneği şöyle yazabiliriz:

#![allow(unused)]
fn main() {
let &[ref x, mut y] = &[(), ()]; // x: &(), mut y: ()
}

ref / ref mut kısıtı

Rust 2021 ve daha eski sürümlerde şuna izin veriliyordu:

#![allow(unused)]
fn main() {
let [ref x] = &[()]; // x: &()
}

Burada açık ref bağlama modu gereksizdir; çünkü paylaşımlı referansı geçmek, yani desende anmamak, bağlama modunu zaten refe çevirir.

Dil için başka olasılıklara yer bırakmak adına, Rust 2024’te gereksiz açık bağlama modlarına izin verilmiyor. Yukarıdaki örnek basitçe şöyle yazılabilir:

#![allow(unused)]
fn main() {
let [x] = &[()]; // x: &()
}

Referans desenleri kısıtı

Rust 2021 ve daha eski sürümlerde şu tuhaflığa izin veriliyordu:

#![allow(unused)]
fn main() {
let [&x, y] = &[&(), &()]; // x: (), y: &&()
}

Burada desendeki &, hem &() içindeki referansla eşleşir hem de varsayılan bağlama modunu yeniden move yapar. Bu şaşırtıcı olabilir; çünkü desendeki tek bir &, beklenenden büyük bir tür değişikliğine yol açıp iki kat referansı birden kaldırır.

Bunu düzeltebilmek için alan bırakmak adına, Rust 2024’te varsayılan bağlama modu move değilken desene & veya &mut yazmak hatadır. Yani & ya da &mut, yalnızca desen o noktaya kadar tamamen açıksa yazılabilir.

Rust 2024’te yukarıdaki örnek şöyle yazılabilir:

#![allow(unused)]
fn main() {
let &[&x, ref y] = &[&(), &()];
}

Taşıma

rust_2024_incompatible_pat lint’i, Rust 2024’te artık izin verilmeyen desenleri işaretler. Bu lint, cargo fix --edition çalıştırılırken otomatik uygulanan rust-2024-compatibility grubunun parçasıdır. Etkilenen desenleri, Rust 2024’te ve önceki tüm sürümlerde doğru çalışan tamamen açık desenlere otomatik olarak dönüştürür.

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() {
let [x, mut y] = &[(), ()];
let [ref x] = &[()];
let [&x, y] = &[&(), &()];
}

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

#![allow(unused)]
fn main() {
let &[ref x, mut y] = &[(), ()];
let &[ref x] = &[()];
let &[&x, ref y] = &[&(), &()];
}

Alternatif olarak, taşınması gereken desenleri bulmak için lint’i elle etkinleştirebilirsiniz:

#![allow(unused)]
fn main() {
// Elle taşıma yapmak için bunu crate köküne ekleyin.
#![warn(rust_2024_incompatible_pat)]
}

Güvensiz extern Blokları

Özet

  • [extern blokları] artık unsafe anahtar kelimesiyle işaretlenmelidir.

Ayrıntılar

Rust 1.82, tüm sürümlerde [extern bloklarını] unsafe anahtar kelimesiyle işaretleme yeteneğini ekledi.1 unsafe anahtar kelimesini eklemek, imzaların doğru olmasını sağlama sorumluluğunun extern bloğunu yazan kişiye ait olduğunu vurgular. İmzalar doğru değilse tanımsız davranış oluşabilir.

Güvensiz bir extern bloğunun sözdizimi şöyledir:

#![allow(unused)]
fn main() {
unsafe extern "C" {
    // sqrt (libm içinden), her `f64` ile çağrılabilir
    pub safe fn sqrt(x: f64) -> f64;

    // strlen (libc içinden), geçerli bir işaretçi ister,
    // bu yüzden onu unsafe fn olarak işaretliyoruz
    pub unsafe fn strlen(p: *const std::ffi::c_char) -> usize;

    // bu fonksiyon safe ya da unsafe demiyor; bu yüzden varsayılanı unsafe olur
    pub fn free(p: *mut core::ffi::c_void);

    pub safe static IMPORTANT_BYTES: [u8; 256];
}
}

Bir extern bloğunu unsafe olarak işaretlemenin yanında, blok içindeki tekil öğelerin safe ya da unsafe olduğunu da belirtebilirsiniz. safe olarak işaretlenen öğeler unsafe blok olmadan kullanılabilir.

2024 sürümüyle birlikte extern bloklarının üzerine unsafe yazmak zorunlu oldu. Bunun amacı, extern tanımlarının uyması gereken güvenlik koşullarını çok açık hale getirmektir.

Taşıma

missing_unsafe_on_extern lint’i, extern bloklarına unsafe anahtar kelimesini ekleyebilir. Bu lint, otomatik sürüm taşımasına dahil olan rust-2024-compatibility lint grubunun parçasıdır. Kodunuzu Rust 2024 ile uyumlu hale getirmek için şunu çalıştırın:

cargo fix --edition

Ancak otomatik taşımanın extern bloğundaki imzaların gerçekten doğru olup olmadığını doğrulayamayacağını unutmayın. Bunları elle gözden geçirmek hâlâ sizin sorumluluğunuzdadır.

Alternatif olarak, güncellenmesi gereken extern bloklarını bulmak için lint’i elle etkinleştirebilirsiniz.

#![allow(unused)]
fn main() {
// Elle taşıma yapmak için bunu crate köküne ekleyin.
#![warn(missing_unsafe_on_extern)]
}

  1. See RFC 3484 for the original proposal.

Güvensiz Öznitelikler

Özet

Ayrıntılar

Rust 1.82, tüm sürümlerde bazı özniteliklerin unsafe olarak işaretlenebilmesini ekledi. Böylece bunların korunması gereken doğruluk/güvenlik koşullarına sahip olduğu belirtilmiş olur.1 Güvensiz bir özniteliğin sözdizimi şöyledir:

#![allow(unused)]
fn main() {
// GUVENLIK: Bu adda başka bir genel fonksiyon yok.
#[unsafe(no_mangle)]
pub fn example() {}
}

Özniteliği unsafe ile işaretlemek, derleyicinin kendi başına doğrulayamayacağı güvenlik koşullarının yerine getirilmesi gerektiğini vurgular.

2024 sürümüyle birlikte bu özniteliklerin unsafe olarak işaretlenmesi zorunlu hale geldi. Aşağıdaki bölümde bu özniteliklerin güvenlik gereksinimleri açıklanır.

Güvenlik gereksinimleri

no_mangle, export_name ve link_section öznitelikleri, öğelerin sembol adlarını ve bağlama davranışını etkiler. Bu özniteliklerin doğru kullanıldığından emin olmak için dikkatli olunmalıdır.

Bağlanan tüm kütüphanelerdeki sembol kümesi tek bir genel ad alanı olduğundan, kütüphaneler arasında sembol adı çakışması varsa sorun çıkabilir. Normal tanımlı fonksiyonlarda bu genelde sorun olmaz; çünkü symbol mangling, sembol adının benzersiz olmasını sağlamaya yardımcı olur. Ancak export_name gibi öznitelikler bu benzersizlik varsayımını bozabilir.

Örneğin önceki sürümlerde aşağıdaki kod, yalnızca güvenli kod içermesine rağmen çoğu Unix benzeri platformda çöker:

fn main() {
    println!("Merhaba, dunya!");
}

#[export_name = "malloc"]
fn foo() -> usize { 1 }

2024 sürümünde bu özniteliklerin unsafe olarak işaretlenmesi zorunludur; böylece sembolün doğru tanımlandığından emin olunması gerektiği vurgulanır:

#![allow(unused)]
fn main() {
// GUVENLIK: loop sembolünün yalnızca tek bir tanımı olmalıdır.
#[unsafe(export_name="loop")]
fn arduino_loop() {
    // ...
}
}

Taşıma

unsafe_attr_outside_unsafe lint’i, bu öznitelikleri unsafe(...) biçimine çevirebilir. Bu lint, otomatik sürüm taşımasına dahil olan rust-2024-compatibility lint grubunun parçasıdır. Kodunuzu Rust 2024 ile uyumlu hale getirmek için şunu çalıştırın:

cargo fix --edition

Ancak otomatik taşımanın bu özniteliklerin doğru kullanılıp kullanılmadığını doğrulayamayacağını unutmayın. Kullanımlarını elle gözden geçirmek hâlâ sizin sorumluluğunuzdadır.

Alternatif olarak, bu özniteliklerin güncellenmesi gereken yerleri bulmak için lint’i elle etkinleştirebilirsiniz.

#![allow(unused)]
fn main() {
// Elle taşıma yapmak için bunu crate köküne ekleyin.
#![warn(unsafe_attr_outside_unsafe)]
}

  1. See RFC 3325 for the original proposal.

unsafe_op_in_unsafe_fn Uyarısı

Özet

  • unsafe_op_in_unsafe_fn lint’i artık varsayılan olarak uyarı verir. Bu uyarı, unsafe fonksiyonların içinde açık unsafe blok olmadan yapılan unsafe işlem çağrılarını tespit eder.

Ayrıntılar

unsafe_op_in_unsafe_fn lint’i, unsafe bir fonksiyon içinde açık bir unsafe {} bloğu olmadan [unsafe işlemler] varsa tetiklenir.

#![allow(unused)]
fn main() {
#![warn(unsafe_op_in_unsafe_fn)]
unsafe fn get_unchecked<T>(x: &[T], i: usize) -> &T {
  x.get_unchecked(i) // UYARI: unsafe blok gerekir
}
}

Çözüm, unsafe işlemleri bir unsafe blok içine almaktır:

#![allow(unused)]
fn main() {
#![deny(unsafe_op_in_unsafe_fn)]
unsafe fn get_unchecked<T>(x: &[T], i: usize) -> &T {
  unsafe { x.get_unchecked(i) }
}
}

Bu değişikliğin amacı, unsafe fonksiyon içinde unsafe işlemlerin yanlışlıkla kullanılmasına karşı koruma sağlamaktır. unsafe fonksiyon anahtar kelimesi eskiden iki görev üstleniyordu. Birincisi, fonksiyonu çağırmanın unsafe olduğunu ve ek güvenlik koşullarını sağlamanın çağıranın sorumluluğunda bulunduğunu bildirmekti. İkincisi ise fonksiyon içinde unsafe işlemlere izin vermekti. Bu ikinci rolün, açık unsafe bloklar olmadan fazla riskli olduğuna karar verildi.

Daha fazla bilgi ve gerekçe için RFC #2585’e bakabilirsiniz.

Taşıma

unsafe_op_in_unsafe_fn lint’i, rust-2024-compatibility lint grubunun parçasıdır. Kodunuzu Rust 2024 ile uyumlu hale getirmek için şunu çalıştırın:

cargo fix --edition

Alternatif olarak, unsafe blok eklenmesi gereken yerleri bulmak için lint’i elle etkinleştirebilir ya da tamamen susturmak için allow yapabilirsiniz.

#![allow(unused)]
fn main() {
// Elle taşıma yapmak için bunu crate köküne ekleyin.
#![warn(unsafe_op_in_unsafe_fn)]
}

static mut Referanslarını Yasaklama

Özet

  • static_mut_refs lint düzeyi artık varsayılan olarak denydir. Bu lint, bir static mut için paylaşımlı ya da değiştirilebilir referans alınıp alınmadığını denetler.

Ayrıntılar

static_mut_refs lint’i, bir static mut değerine referans alınmasını tespit eder. 2024 sürümünde bu lint, bu tür referanslardan kaçınmanız gerektiğini vurgulamak için varsayılan olarak deny haline getirildi.

#![allow(unused)]
fn main() {
static mut X: i32 = 23;
static mut Y: i32 = 24;

unsafe {
    let y = &X;             // HATA: değiştirilebilir static'e paylaşımlı referans
    let ref x = X;          // HATA: değiştirilebilir static'e paylaşımlı referans
    let (x, y) = (&X, &Y);  // HATA: değiştirilebilir static'e paylaşımlı referans
}
}

Rust’ın değiştirilebilirlik XOR takma ad kuralını ihlal ederek böyle bir referansı almak, referans hiç okunmasa ya da yazılmasa bile, her zaman anında tanımsız davranış olmuştur. Dahası, bir static mut için bu kuralı sağlamak, kod hakkında küresel ölçekte akıl yürütmeyi gerektirir; bu da yeniden giriş ve/veya çok iş parçacıklılığı söz konusu olduğunda özellikle zordur.

Görünür bir & işleci olmadan örtük referansların otomatik oluşturulduğu bazı durumlar da vardır. Örneğin aşağıdaki kullanımlar da lint’i tetikler:

#![allow(unused)]
fn main() {
static mut NUMS: &[u8; 3] = &[0, 1, 2];

unsafe {
    println!("{NUMS:?}");   // HATA: değiştirilebilir static'e paylaşımlı referans
    let n = NUMS.len();     // HATA: değiştirilebilir static'e paylaşımlı referans
}
}

Alternatifler

Mümkün olan her durumda, bunun yerine yerel olarak akıl yürütülebilen bir soyutlama arkasında içsel değiştirilebilirlik sağlayan bir türün değiştirilemez static halini kullanmanız şiddetle tavsiye edilir. Bu yaklaşım, Rust’ın değiştirilebilirlik XOR takma ad kuralını korumanın karmaşıklığını büyük ölçüde azaltır.

Yerel olarak akıl yürütülebilen bir soyutlamanın mümkün olmadığı ve bu yüzden static değişkeninize erişimler hakkında hâlâ küresel düzeyde düşünmek zorunda kaldığınız durumlarda, artık &raw const veya &raw mut işleçleri ile elde edilenler gibi ham işaretçiler kullanmalısınız. Doğrudan referans almak yerine önce ham işaretçi elde etmek, o işaretçi üzerinden yapılacak erişimlerin güvenlik gereksinimlerini unsafe geliştiricileri için daha tanıdık hale getirir ve bunları daha küçük kod bölgelerine ertelemeyi veya sınırlamayı sağlar.

Aşağıdaki örneklerin yalnızca fikir vermek için bulunduğunu unutmayın; bunlar tam teşekküllü uygulamalar değildir. Bunları olduğu gibi kopyalamayın. Kendi durumunuz, ihtiyaçlarınıza göre değişiklik yapmanızı gerektirecek ayrıntılar içerebilir. Bu örnekler, soruna yaklaşmanın farklı yollarını göstermek içindir.

Standart kütüphanedeki ilgili türlerin belgelerini, tanımsız davranış referansını, Rustonomicon’u okumanız ve sorularınız varsa Users Forum gibi Rust forumlarından birine danışmanız önerilir.

Globalleri kullanmayın

Muhtemelen bunu zaten biliyorsunuz; ama mümkünse değiştirilebilir genel durumdan kaçınmak en iyisidir. Elbette bu bazen biraz hantal ya da zor olabilir; özellikle birçok fonksiyon arasında değiştirilebilir referans taşımak gerekiyorsa.

Atomics

atomik türler, static içinde (mut olmadan) kullanılabilecek tamsayılar, işaretçiler ve boole türleri sağlar.

use std::sync::atomic::Ordering;
use std::sync::atomic::AtomicU64;

// Şundan:
//   static mut COUNTER: u64 = 0;
// buna geçin:
static COUNTER: AtomicU64 = AtomicU64::new(0);

fn main() {
    // Kullanım durumunuzu analiz edip doğru Ordering seçtiğinizden emin olun.
    COUNTER.fetch_add(1, Ordering::Relaxed);
}

Mutex or RwLock

Türünüz bir atomikten daha karmaşıksa, genel değere doğru erişimi sağlamak için Mutex ya da RwLock kullanmayı düşünün.

use std::sync::Mutex;
use std::collections::VecDeque;

// Şundan:
//     static mut QUEUE: VecDeque<String> = VecDeque::new();
// buna geçin:
static QUEUE: Mutex<VecDeque<String>> = Mutex::new(VecDeque::new());

fn main() {
    QUEUE.lock().unwrap().push_back(String::from("abc"));
    let first = QUEUE.lock().unwrap().pop_front();
}

OnceLock or LazyLock

Eğer static mut kullanma nedeniniz const olamayan tek seferlik bir ilk kurulumsa, bunun yerine OnceLock ya da LazyLock kullanabilirsiniz.

use std::sync::LazyLock;

struct GlobalState;

impl GlobalState {
    fn new() -> GlobalState {
        GlobalState
    }
    fn example(&self) {}
}

// Şunun gibi geçici ya da başlatılmamış bir tür yerine:
//     static mut STATE: Option<GlobalState> = None;
// şunu kullanın:
static STATE: LazyLock<GlobalState> = LazyLock::new(|| {
    GlobalState::new()
});

fn main() {
    STATE.example();
}

OnceLock, LazyLock ile benzerdir; ancak kurucuya bilgi geçirmeniz gerekiyorsa kullanılabilir. Bu, main gibi tekil ilk kurulum noktalarıyla veya girdiler genel değere erişilen her yerde mevcutsa iyi çalışır.

use std::sync::OnceLock;

struct GlobalState;

impl GlobalState {
    fn new(verbose: bool) -> GlobalState {
        GlobalState
    }
    fn example(&self) {}
}

struct Args {
    verbose: bool
}
fn parse_arguments() -> Args {
    Args { verbose: true }
}

static STATE: OnceLock<GlobalState> = OnceLock::new();

fn main() {
    let args = parse_arguments();
    let state = GlobalState::new(args.verbose);
    let _ = STATE.set(state);
    // ...
    STATE.get().unwrap().example();
}

no_std tek seferlik ilk kurulum

Bu örnek, genel bir değerin tek seferlik başlatılmasını sağlama açısından OnceLock benzeridir; ama std gerektirmez, bu da no_std bağlamında yararlıdır. Hedefiniz atomikleri destekliyorsa genel değerin başlatılıp başlatılmadığını denetlemek için bir atomik kullanabilirsiniz. Örnek desen şuna benzer:

use core::sync::atomic::AtomicUsize;
use core::sync::atomic::Ordering;

struct Args {
    verbose: bool,
}
fn parse_arguments() -> Args {
    Args { verbose: true }
}

struct GlobalState {
    verbose: bool,
}

impl GlobalState {
    const fn default() -> GlobalState {
        GlobalState { verbose: false }
    }
    fn new(verbose: bool) -> GlobalState {
        GlobalState { verbose }
    }
    fn example(&self) {}
}

const UNINITIALIZED: usize = 0;
const INITIALIZING: usize = 1;
const INITIALIZED: usize = 2;

static STATE_INITIALIZED: AtomicUsize = AtomicUsize::new(UNINITIALIZED);
static mut STATE: GlobalState = GlobalState::default();

fn set_global_state(state: GlobalState) {
    if STATE_INITIALIZED
        .compare_exchange(
            UNINITIALIZED,
            INITIALIZING,
            Ordering::SeqCst,
            Ordering::SeqCst,
        )
        .is_ok()
    {
        // GUVENLIK: STATE üzerindeki okuma/yazmalar INITIALIZED korumasıyla korunuyor.
        unsafe {
            STATE = state;
        }
        STATE_INITIALIZED.store(INITIALIZED, Ordering::SeqCst);
    } else {
        panic!("zaten baslatilmis ya da eszamanli baslatma var");
    }
}

fn get_state() -> &'static GlobalState {
    if STATE_INITIALIZED.load(Ordering::Acquire) != INITIALIZED {
        panic!("baslatilmamis");
    } else {
        // GUVENLIK: Durum baslatildiktan sonra degistirilebilir erisim mumkun degil.
        unsafe { &*&raw const STATE }
    }
}

fn main() {
    let args = parse_arguments();
    let state = GlobalState::new(args.verbose);
    set_global_state(state);
    // ...
    let state = get_state();
    state.example();
}

Bu örnek, static içine başlatılmadan önce bir varsayılan değer koyabildiğinizi varsayar; burada örneğin const default kurucusu kullanılıyor. Bu mümkün değilse MaybeUninit, ya da dinamik trait gönderimi (trait uygulayan sahte bir türle) veya varsayılan yer tutucu sağlayan başka bir yaklaşım düşünebilirsiniz.

Topluluk tarafından sunulan, benzer tek seferlik ilk kurulum sağlayan crate’ler de vardır. Örneğin static-cell crate’i, portable-atomic kullanarak atomik olmayan hedefleri de destekler.

Ham işaretçiler

Bazı durumlarda static mut kullanmaya devam edebilir, ama referans oluşturmaktan kaçınabilirsiniz. Örneğin yalnızca bir C kütüphanesine [ham işaretçi] geçirmeniz gerekiyorsa ara referans oluşturmayın. Bunun yerine [ham ödünç alma işleçlerini] kullanabilirsiniz:

#[repr(C)]
struct GlobalState {
    value: i32
}

impl GlobalState {
    const fn new() -> GlobalState {
        GlobalState { value: 0 }
    }
}

static mut STATE: GlobalState = GlobalState::new();

unsafe extern "C" {
    fn example_ffi(state: *mut GlobalState);
}

fn main() {
    unsafe {
        // Şundan:
        //     example_ffi(&mut STATE as *mut GlobalState);
        // buna geçin:
        example_ffi(&raw mut STATE);
    }
}

Yine de değiştirilebilir işaretçiler etrafındaki takma ad kısıtlarını korumanız gerektiğini unutmayın. Bunun için iş parçacıkları, kesme işleyicileri ve yeniden giriş arasında kullanım biçimine dair iç ya da dış eşzamanlama veya kanıtlar gerekebilir.

Sync ile UnsafeCell

UnsafeCell Sync uygulamaz; bu yüzden bir static içinde kullanılamaz. İçsel değiştirilebilirlik sağlamak için UnsafeCell etrafında kendi sarmalayıcınızı oluşturup ona Sync uygulaması ekleyebilirsiniz. Bu yaklaşım, değiştirilebilir işaretçiler için gereken güvenlik değişmezlerini sağlayan dış kilitleriniz ya da başka garantileriniz varsa yararlı olabilir.

Bunun büyük ölçüde ham işaretçiler örneğiyle aynı olduğunu unutmayın. Sarmalayıcı, türü nasıl kullandığınızı daha görünür kılar ve hangi güvenlik koşullarına dikkat etmeniz gerektiğine odaklanmanıza yardım eder. Onun dışında yaklaşım kabaca aynıdır.

#![allow(unused)]
fn main() {
use std::cell::UnsafeCell;

fn with_interrupts_disabled<T: Fn()>(f: T) {
    // Gerçek bir örnek kesmeleri kapatırdı.
    f();
}

#[repr(C)]
struct GlobalState {
    value: i32,
}

impl GlobalState {
    const fn new() -> GlobalState {
        GlobalState { value: 0 }
    }
}

#[repr(transparent)]
pub struct SyncUnsafeCell<T>(UnsafeCell<T>);

unsafe impl<T: Sync> Sync for SyncUnsafeCell<T> {}

static STATE: SyncUnsafeCell<GlobalState> = SyncUnsafeCell(UnsafeCell::new(GlobalState::new()));

fn set_value(value: i32) {
    with_interrupts_disabled(|| {
        let state = STATE.0.get();
        unsafe {
            // GUVENLIK: Bu deger yalnizca kesme isleyicisinde okunuyor,
            // kesmeler kapatildi ve bunu yalnizca tek bir is parcaciginda kullaniyoruz.
            (*state).value = value;
        }
    });
}
}

Standart kütüphanede UnsafeCell’in SyncUnsafeCell adında yalnızca nightly’de bulunan kararsız bir çeşidi vardır. Yukarıdaki örnek standart kütüphane türünün çok sadeleştirilmiş bir sürümünü gösterir; ama kullanım mantığı benzerdir. Daha iyi yalıtım sağlayabilir, bu yüzden ayrıntılar için gerçek uygulamasına bakmak faydalıdır.

Bu örnek, gömülü ortamlarda görebileceğiniz türden hayalî bir with_interrupts_disabled fonksiyonu içerir. Örneğin critical-section crate’i, gömülü ortamlar için benzer bir işlevsellik sağlar.

Güvenli referanslar

Bazı durumlarda bir static mut için referans oluşturmak güvenli olabilir. static_mut_refs lint’inin asıl vurguladığı nokta, bunun doğru yapılmasının çok zor olmasıdır. Ama bu imkansız demek değildir. Eğer takma ad gereksinimlerinin korunduğunu garanti edebiliyorsanız, örneğin static değeri dar bir kapsamda kullanıyorsanız, iç ya da dış eşzamanlama sağladıysanız, kesme işleyicileri, yeniden giriş, panic güvenliği, drop işleyicileri gibi durumları hesaba kattıysanız, referans almak uygun olabilir.

Bunun için iki yaklaşım vardır. Ya static_mut_refs lint’ine izin verirsiniz (tercihen olabildiğince dar kapsamda), ya da &mut *&raw mut MY_STATIC örneğinde olduğu gibi ham işaretçileri referansa dönüştürürsünüz.

Kısa ömürlü referanslar

Bir static mut için referans oluşturmanız şartsa, bu referansın ne kadar süre var olduğunu mümkün olduğunca dar tutmanız önerilir. Referansı bir yerde saklamaktan ya da kodun büyük bir bölümü boyunca canlı tutmaktan kaçının. Kısa ömürlü tutmak, denetlemeyi ve erişimin tüm süre boyunca gerçekten tekil olduğunu doğrulamayı kolaylaştırır. Varsayılan birim olarak işaretçileri kullanın; yalnızca gerçekten gerekliyse işaretçiyi referansa çevirin.

Taşıma

static mut referanslarını düzeltmek için otomatik taşıma yoktur. Tanımsız davranıştan kaçınmak için kodunuzu Alternatifler bölümünde önerildiği gibi farklı bir yaklaşımla yeniden yazmanız gerekir.

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_unsafe lint’i artık varsayılan olarak denydir.

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
};
}

Makro Parçacık Belirteçleri

Özet

  • expr parçacık belirteci artık const ve _ ifadelerini de destekler.
  • Geriye dönük uyumluluk için expr_2021 parçacık belirteci eklendi.

Ayrıntılar

Rust’a yeni sözdizimleri eklendikçe, geriye dönük uyumluluğu korumak için mevcut macro_rules parçacık belirteçlerinin bu yeni sözdizimiyle eşleşmesine bazen izin verilmez. Eski belirteçlerin yeni sözdizimini desteklemesi kimi zaman bir sonraki sürüme ertelenir; bu da onları güncelleme fırsatı yaratır.

Nitekim bu durum, 1.79’da gelen const ifadeleri ve 1.59’da gelen _ ifadeleri için yaşandı. 2021 sürümünde ve daha eski sürümlerde expr parçacık belirteci bu ifadelerle eşleşmez. Çünkü şöyle bir senaryo olabilir:

macro_rules! example {
    ($e:expr) => { println!("ilk kural"); };
    (const $e:expr) => { println!("ikinci kural"); };
}

fn main() {
    example!(const { 1 + 1 });
}

Burada 2021 sürümünde makro ikinci kuralla eşleşir. Eğer daha eski sürümlerde expr, yeni gelen const ifadeleriyle de eşleşecek şekilde değiştirilmiş olsaydı, makro ilk kuralla eşleşirdi ve bu kırıcı bir değişiklik olurdu.

2024 sürümünde expr belirteçleri artık const ve _ ifadeleriyle de eşleşir. Eski davranışı desteklemek için, bu yeni ifadelerle eşleşmeyen expr_2021 parçacık belirteci eklendi.

Taşıma

edition_2024_expr_fragment_specifier lint’i, mevcut makroların davranışı değişmesin diye expr belirtecinin tüm kullanımlarını expr_2021e çevirir. Bu lint, otomatik sürüm taşımasına dahil olan rust-2024-compatibility lint grubunun parçasıdır. Kodunuzu Rust 2024 ile uyumlu hale getirmek için şunu çalıştırın:

cargo fix --edition

Çoğu durumda, yeni ifadeleri desteklemek için muhtemelen expr belirtecini korumak istersiniz. Bunun için makronuzu gözden geçirip const ya da _ ile eşleşebilecek başka kurallar bulunup bulunmadığını ve çatışma olup olmadığını kontrol etmeniz gerekir. Yeni davranışı istiyorsanız lint’in yaptığı değişiklikleri geri alın.

Alternatif olarak, expr belirtecini güncellemeniz gerekebilecek makroları bulmak için lint’i elle etkinleştirebilirsiniz.

#![allow(unused)]
fn main() {
// Elle taşıma yapmak için bunu crate köküne ekleyin.
#![warn(edition_2024_expr_fragment_specifier)]
}

Eksik Makro Parçacık Belirteçleri

NOT: Bu durum başlangıçta yalnızca 2024 sürümü için kesin hata yapıldı. Rust 2024’ten sonra yayımlanan Rust 1.89 ile birlikte lint tüm sürümlerde kesin hata haline getirildi.

Özet

  • missing_fragment_specifier lint’i artık kesin hatadır.

Ayrıntılar

missing_fragment_specifier lint’i, macro_rules! makro tanımındaki kullanılmayan bir desende, parçacık belirteciyle (örneğin :expr) izlenmeyen bir meta değişken ($e gibi) bulunduğu durumu tespit eder. Bu durum 2024 sürümünde kesin hata yapıldı.

macro_rules! foo {
   () => {};
   ($name) => { }; // HATA: parçacık belirteci eksik
}

fn main() {
   foo!();
}

Eksik belirteçli bir kuralla eşleşecek argümanlarla makroyu çağırmak (örneğin foo!($name)), tüm sürümlerde zaten kesin hataydı. Ancak yalnızca eksik parçacık belirteçli makro tanımlamak öyle değildi; yine de Rust 1.17’de bunun için bir lint eklenmişti.

Taşıma

Kodunuzu 2024 sürümüne taşımak için makrodaki kullanılmayan eşleştirici kuralı kaldırın.

Bu değişiklik için otomatik taşıma yoktur. Bu makro tarzının son derece nadir olduğunu düşünüyoruz. Lint, Rust 1.17’den beri gelecekte uyumsuzluk lint’i, Rust 1.20’den beri varsayılan deny lint’i, Rust 1.82’den itibaren bu kalıbı kullanan bağımlılıklar için uyarı veren bir lint idi; Rust 1.89’da ise kesin hata haline geldi.

gen Anahtar Kelimesi

Özet

Ayrıntılar

gen anahtar kelimesi, Rust’ın gelecekteki bir sürümünde “gen blokları“nı getirebilmek için RFC #3513 kapsamında ayrıldı. gen blokları, belirli türde yineleyicileri yazmayı kolaylaştıran bir yol sunacak. Bu anahtar kelimeyi şimdiden ayırmak, bir sonraki sürüm gelmeden gen bloklarını kararlı hale getirmeyi kolaylaştıracak.

Taşıma

gen anahtar kelimesinin eklenmesi, adı zaten gen olan tanımlayıcılar için sorun yaratabilir. Örneğin gen adlı bir değişken ya da fonksiyon adı artık yeni anahtar kelimeyle çakışır. Bunu aşmak için Rust, ham tanımlayıcı için r# önekini destekler; böylece tanımlayıcılar anahtar kelimelerle çakışabilir.

keyword_idents_2024 lint’i, gen adlı tüm tanımlayıcıları otomatik olarak r#gen haline getirir; böylece kod her iki sürümde de çalışmaya devam eder. Bu lint, cargo fix --edition çalıştırıldığında otomatik uygulanan rust-2024-compatibility grubunun parçasıdır. Kodunuzu Rust 2024 ile uyumlu hale getirmek için şunu çalıştırın:

cargo fix --edition

Örneğin bu komut şunu:

fn gen() {
    println!("uretiliyor!");
}

fn main() {
    gen();
}

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

fn r#gen() {
    println!("uretiliyor!");
}

fn main() {
    r#gen();
}

Alternatif olarak, gen tanımlayıcılarının r#gen yapılması gereken yerleri bulmak için lint’i elle etkinleştirebilirsiniz:

#![allow(unused)]
fn main() {
// Elle taşıma yapmak için bunu crate köküne ekleyin.
#![warn(keyword_idents_2024)]
}

Ayrılmış Söz Dizimi

Özet

  • #"foo"# biçimindeki öneksiz korumalı dizgiler gelecekteki kullanım için ayrılmıştır.
  • Art arda gelen iki ya da daha fazla # karakteri gelecekteki kullanım için ayrılmıştır.

Ayrıntılar

RFC 3593, 2024 sürümünde öneki olmayan korumalı dizgi sabitleri için sözdizimini ayırdı; böylece gelecekteki olası dil değişikliklerine yer açılmış oldu. 2021 sürümü, ident##"foo"## gibi önekli korumalı dizgiler için ayrılmış söz dizimini zaten tanımlamıştı. 2024 sürümü bunu, ident öneki olmayan dizgileri de kapsayacak şekilde genişletir.

İki ayrı ayrılmış sözdizimi vardır:

  • Hemen ardından bir [dizgi sabiti] gelen bir ya da daha fazla # karakteri.
  • Arada boşluk olmadan art arda gelen iki ya da daha fazla # karakteri.

Bu ayırma işlemi, tokenlaştırma ve makrolarla etkileşim nedeniyle bir sürüm sınırında yapılıyor. Örneğin şu makroyu düşünün:

#![allow(unused)]
fn main() {
macro_rules! demo {
    ( $a:tt ) => { println!("tek token") };
    ( $a:tt $b:tt $c:tt ) => { println!("uc token") };
}

demo!("foo");
demo!(r#"foo"#);
demo!(#"foo"#);
demo!(###)
}

2024 sürümünden önce bu makro şu çıktıyı üretir:

tek token
tek token
uc token
uc token

2024 sürümünden itibaren #"foo"# satırı ile ### satırı artık derleme hatası üretir; çünkü bu biçimler artık ayrılmıştır.

Taşıma

rust_2024_guarded_string_incompatible_syntax lint’i, ayrılmış sözdizimiyle eşleşen token’ları tespit eder ve bunların ayrı ayrı ayrıştırılmaya devam edebilmesi için gereken yerlere boşluk ekleyen bir değişiklik önerir.

Bu lint, otomatik sürüm taşımasına dahil olan rust-2024-compatibility grubunun parçasıdır. Kodunuzu Rust 2024 ile uyumlu hale getirmek için şunu çalıştırın:

cargo fix --edition

Alternatif olarak, token’ları güncellemeniz gerekebilecek makro çağrılarını bulmak için lint’i elle etkinleştirebilirsiniz:

#![allow(unused)]
fn main() {
// Elle taşıma yapmak için bunu crate köküne ekleyin.
#![warn(rust_2024_guarded_string_incompatible_syntax)]
}

Standart Kütüphane

Aşağıdaki bölümler, 2024 sürümündeki standart kütüphane değişikliklerini ayrıntılı olarak açıklar.

Prelude’deki Değişiklikler

Özet

  • Future ve IntoFuture trait’leri artık prelude’un parçasıdır.
  • Bu durum trait metod çağrılarını belirsiz hale getirip 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 içeren modüldür. Option, Vec, drop ve Clone gibi sık kullanılan öğeleri barındırır.

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ımlıysa, use ornek::*; ifadesi Option adını açıkça 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 BenimYoklayici trait’inden gelen x.poll() çağrısı, std içindeki Future da içe aktarıldığında derlenmeyebilir; çünkü poll çağrısı artık belirsiz hale gelir ve iki trait’ten de gelebilir.

Çözüm olarak Rust 2024 yeni bir prelude kullanır. Bu yeni sürüm, aşağıdaki değişiklikler dışında mevcut prelude ile aynıdır:

Taşıma

Çakışan trait metodları

Kapsamda bulunan iki trait aynı metod adına sahipse, hangi trait metodunun kullanılacağı belirsiz olur. Örneğin:

trait BenimYoklayici {
    // Bu ad, `std` içindeki `Future` trait'inin `poll` metoduyla aynıdır.
    fn poll(&self) {
        println!("yoklaniyor");
    }
}

impl<T> BenimYoklayici for T {}

fn main() {
    // Pin<&mut async {}>, hem `std::future::Future` hem `BenimYoklayici` uygular.
    // Eğer iki trait de kapsamdaysa (Rust 2024'te olduğu gibi),
    // hangi `poll` metodunun çağrılacağı belirsizleşir
    core::pin::pin!(async {}).poll();
}

Bunu tüm sürümlerde çalışacak şekilde tam nitelikli sözdizimiyle düzeltebiliriz:

fn main() {
    // Artık hangi trait metoduna başvurduğumuz açık
    <_ as BenimYoklayici>::poll(&core::pin::pin!(async {}));
}

rust_2024_prelude_collisions lint’i, belirsiz tüm metod çağrılarını otomatik olarak tam nitelikli sözdizimine çevirebilir. Bu lint, cargo fix --edition çalıştırıldığında otomatik uygulanan rust-2024-compatibility grubunun parçasıdır. Kodunuzu Rust 2024 ile uyumlu hale getirmek için şunu çalıştırın:

cargo fix --edition

Alternatif olarak, bu nitelemelerin eklenmesi gereken yerleri bulmak için lint’i elle etkinleştirebilirsiniz:

#![allow(unused)]
fn main() {
// Elle taşıma yapmak için bunu crate köküne ekleyin.
#![warn(rust_2024_prelude_collisions)]
}

Box<[T]> İçin IntoIterator Eklenmesi

Özet

  • Kutulanmış dilimler tüm sürümlerde IntoIterator uygular.
  • Metod çağrısı sözdizimi kullanıldığında (boxed_slice.into_iter() gibi), IntoIterator::into_iter çağrıları 2024 öncesi sürümlerde gizlenir. Bu yüzden boxed_slice.into_iter(), eskiden olduğu gibi hâlâ (&(*boxed_slice)).into_iter() olarak çözülür.
  • boxed_slice.into_iter(), Rust 2024’te IntoIterator::into_iter çağrısı anlamına gelir.

Ayrıntılar

Rust 1.80’e kadar kutulanmış dilimler için IntoIterator uygulanmamıştı. Önceki sürümlerde kutulanmış bir dilim üzerinde .into_iter() çağırırsanız, metod çağrısı otomatik olarak Box<[T]> değerini &[T]e dereference eder ve &T referansları döndüren bir yineleyici üretirdi. Örneğin şu kullanım önceki sürümlerde çalışıyordu:

#![allow(unused)]
fn main() {
// Önceki sürümlerdeki davranış örneği.
let benim_kutulu_dilimim: Box<[u32]> = vec![1, 2, 3].into_boxed_slice();
// Not: 1.80'den eski sürümlerde .into_iter() gerekliydi
for x in benim_kutulu_dilimim.into_iter() {
    // 2024 öncesi sürümlerde x, &u32 türündedir
}
}

Rust 1.80’de kutulanmış dilimler için IntoIterator uygulamaları eklendi. Böylece dilimin öğeleri üzerinde referansla değil, değeri sahiplenerek dolaşmak mümkün oldu:

#![allow(unused)]
fn main() {
// 1.80 ile yeni, tüm sürümlerde
let benim_kutulu_dilimim: Box<[u32]> = vec![1, 2, 3].into_boxed_slice();
for x in benim_kutulu_dilimim { // dikkat: .into_iter() çağrısı gerekmiyor
    // x, u32 türündedir
}
}

Bu örnek tüm sürümlerde geçerlidir; çünkü daha önce bu kullanım hataydı. for döngüleri, .into_iter() metod çağrısının yaptığı gibi otomatik dereference yapmaz.

Ancak bu normalde kırıcı bir değişiklik olurdu; çünkü kutulanmış dilim üzerinde elle .into_iter() çağıran mevcut kod, referanslar üzerinde dönen yineleyiciden değerler üzerinde dönen yineleyiciye dönüşecekti. Bu sorunu çözmek için kutulanmış dilimlerde .into_iter() metod çağrıları sürüme bağlı davranır. 2024’ten önceki sürümlerde referans yineleyicisi döndürmeye devam eder; 2024 ile birlikte ise değer yineleyicisi döndürür.

#![allow(unused)]
fn main() {
// 2024 sürümündeki değişmiş davranış örneği
let benim_kutulu_dilimim: Box<[u32]> = vec![1, 2, 3].into_boxed_slice();
// Hâlâ elle .into_iter() çağıran eski kod örneği
for x in benim_kutulu_dilimim.into_iter() {
    // 2024 sürümünde x artık u32 türündedir
}
}

Taşıma

boxed_slice_into_iter lint’i, kutulanmış dilimler üzerindeki .into_iter() çağrılarını, referans üretmeye devam eden eski davranışı korumak için otomatik olarak .iter() çağrılarına dönüştürür. Bu lint, cargo fix --edition çalıştırıldığında otomatik uygulanan rust-2024-compatibility grubunun parçasıdır. Kodunuzu Rust 2024 ile uyumlu hale getirmek için şunu çalıştırın:

cargo fix --edition

Örneğin bu komut şunu:

fn main() {
    let benim_kutulu_dilimim: Box<[u32]> = vec![1, 2, 3].into_boxed_slice();
    for x in benim_kutulu_dilimim.into_iter() {
        // x, &u32 türündedir
    }
}

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

fn main() {
    let benim_kutulu_dilimim: Box<[u32]> = vec![1, 2, 3].into_boxed_slice();
    for x in benim_kutulu_dilimim.iter() {
        // x, &u32 türündedir
    }
}

boxed_slice_into_iter lint’i tüm sürümlerde varsayılan olarak uyarı verir; bu yüzden lint’i özellikle susturmadıysanız taşıma öncesinde de bunu görmüş olmanız gerekir.

Yeni Güvensiz İşlevler

Özet

Ayrıntılar

Zamanla, standart kütüphanedeki bazı işlevlerin aslında unsafe olarak işaretlenmiş olması gerektiği ortaya çıktı. Ancak bir işleve unsafe eklemek, mevcut kodun unsafe blok içine alınmasını gerektirdiği için kırıcı bir değişiklik olabilir. Bu kırılmayı önlemek için, bu işlevler önceki sürümlerde unsafe gerektirmemeye devam ederken 2024 sürümünden itibaren unsafe olarak işaretlenir.

std::env::{set_var, remove_var}

Bazı platformlarda süreç ortamının ele alınış biçiminin güvenlik sınırlamaları yüzünden, çok iş parçacıklı bir programda std::env::set_var ya da std::env::remove_var çağırmak güvensiz olabilir. Standart kütüphane bu işlevleri başlangıçta güvenli olarak tanımlamıştı; ancak sonradan bunun doğru olmadığı anlaşıldı.

Bu işlevlerin başka herhangi bir iş parçacığı çalışıyor olabilecekken çağrılmadığından emin olmak önemlidir. Ayrıntılar için işlev belgelerindeki Safety bölümüne bakın.

std::os::unix::process::CommandExt::before_exec

std::os::unix::process::CommandExt::before_exec işlevi, exec çağrısından önce bir kapanışı çalıştırmanın yolunu sunan unix’e özgü bir işlevdir. Bu işlev 1.37 sürümünde kullanımdan kaldırıldı ve yerine aynı işi yapan ama unsafe olarak işaretlenmiş pre_exec getirildi.

before_exec kullanımdan kaldırılmış olsa da, 2024 sürümüyle birlikte doğru biçimde unsafe olarak işaretlenmiştir. Bu sayede henüz pre_exece taşınmamış eski kodların da unsafe blok gerektirdiği açık hale gelir.

before_exec kapanışının sağlaması gereken çok katı güvenlik gereksinimleri vardır. Ayrıntılar için Safety section bölümüne bakın.

Taşıma

Kodunuzun hem 2021 hem 2024 sürümlerinde derlenebilmesi için, bu işlevlerin yalnızca unsafe blokların içinden çağrıldığından emin olmanız gerekir.

Dikkat: Bu işlev çağrılarını elle incelemeniz ve gerekirse bu işlevlerin önkoşullarını sağlayacak şekilde kodunuzu yeniden yazmanız önemlidir. Özellikle set_var ve remove_var, birden fazla iş parçacığı çalışıyor olabilirse çağrılmamalıdır. Kullanım durumunuzu yönetmek için ortam değişkenlerinden başka bir mekanizma seçmeniz gerekebilir.

deprecated_safe_2024 lint’i, bu işlevlerin tüm kullanımlarını otomatik olarak bir unsafe blok içine alır; böylece kod iki sürümde de derlenebilir. Bu lint, cargo fix --edition çalıştırıldığında otomatik uygulanan rust-2024-compatibility grubunun parçasıdır. Kodunuzu Rust 2024 ile uyumlu hale getirmek için şunu çalıştırın:

cargo fix --edition

Örneğin bu komut şunu:

fn main() {
    std::env::set_var("FOO", "123");
}

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

fn main() {
    // YAPILACAK: Ortam erisiminin yalnizca tek is parcacikli kodda oldugunu denetleyin.
    unsafe { std::env::set_var("FOO", "123") };
}

Ancak otomatik taşımanın bu işlevlerin doğru kullanılıp kullanılmadığını doğrulayamayacağını unutmayın. Bunları elle gözden geçirmek hâlâ sizin sorumluluğunuzdadır.

Alternatif olarak, bu işlevlerin çağrıldığı yerleri bulmak için lint’i elle etkinleştirebilirsiniz:

#![allow(unused)]
fn main() {
// Elle taşıma yapmak için bunu crate köküne ekleyin.
#![warn(deprecated_safe_2024)]
}

Cargo

Aşağıdaki bölümler, 2024 sürümündeki Cargo değişikliklerini ayrıntılı olarak açıklar.

Cargo: Rust Sürümünü Bilen Çözücü

Özet

  • Cargo.toml içindeki edition = "2024" ayarı, Rust sürümünü bilen bir bağımlılık çözücüsünü etkinleştiren resolver = "3" anlamına gelir.

Ayrıntılar

Rust 1.84.0’dan beri Cargo, .cargo/config.toml içinde resolver.incompatible-rust-version = "fallback" ayarı verilerek bağımlılık sürümleri seçilirken package.rust-version uyumluluğunun dikkate alınmasına isteğe bağlı olarak destek verir.

Rust 2024 ile birlikte bu varsayılan hale gelir. Yani Cargo.toml içine edition = "2024" yazmak resolver = "3" anlamına gelir; bu da resolver.incompatible-rust-version = "fallback" ayarını ima eder.

Çözücü, bir çalışma alanı için geçerli olan genel bir ayardır ve bağımlılıkların içinde yazıldığında dikkate alınmaz. Bu ayar yalnızca çalışma alanının en üst düzey paketinde geçerlidir. Eğer bir sanal çalışma alanı kullanıyorsanız, yeni çözücüyü etkinleştirmek için [workspace] tanımında [resolver alanını] açıkça ayarlamanız gerekir.

Rust sürümünü bilen bağımlılık çözümlemesinin nasıl çalıştığına dair daha fazla ayrıntı için Cargo kitabına bakın.

Taşıma

Yeni çözücüye geçiş için otomatik taşıma araçları yoktur.

Projelerin, CI içinde en güncel bağımlılıklarla doğrulama yapmasını öneriyoruz; böylece bağımlılıklardaki hatalar mümkün olduğunca erken yakalanır.

Cargo: Tablo ve Anahtar Adı Tutarlılığı

Özet

  • Cargo.toml içinde, aynı şeyi belirtmenin iki farklı yolu bulunan bazı tablo ve anahtar adları kaldırıldı.
    • [project] kaldırıldı; yerine [package] kullanın.
    • default_features kaldırıldı; yerine default-features kullanın.
    • crate_type kaldırıldı; yerine crate-type kullanın.
    • proc_macro kaldırıldı; yerine proc-macro kullanın.
    • dev_dependencies kaldırıldı; yerine dev-dependencies kullanın.
    • build_dependencies kaldırıldı; yerine build-dependencies kullanın.

Ayrıntılar

2024 sürümünde bazı tablo ve anahtar adlarına artık izin verilmiyor. Bunları belirtmenin iki farklı yolu vardı; bu değişiklik her şeyi tek bir biçimde yazmayı garanti etmeye yardımcı olur.

Bunların bazıları zaman içinde değişen kararların sonucu, bazıları ise uygulamanın istemeden oluşmuş kalıntılarıydı. Karışıklığı önlemek ve tablo ile anahtarlar için tek bir yazım tarzını zorunlu kılmak adına artık yalnızca tek varyanta izin veriliyor.

Örneğin:

[dev_dependencies]
rand = { version = "0.8.5", default_features = false }

şuna çevrilmelidir:

[dev-dependencies]
rand = { version = "0.8.5", default-features = false }

Burada dev_dependencies ve default_features için alt çizgilerin kısa çizgiye çevrildiğine dikkat edin.

Taşıma

cargo fix --edition kullanıldığında Cargo, Cargo.toml dosyanızı tercih edilen tablo ve anahtar adlarını kullanacak şekilde otomatik günceller.

Cargo.toml dosyanızı elle güncellemek isterseniz, yukarıdaki listeyi tek tek kontrol edip yalnızca yeni biçimlerin kullanıldığından emin olun.

Cargo: Kullanılmayan Kalıtılmış default-features Alanlarını Reddetme

Özet

  • Çalışma alanı bağımlılığı default-features = true belirtiyorsa (ya da default-features hiç belirtmiyorsa), kalıtılan çalışma alanı bağımlılığında default-features = false artık kullanılamaz.

Ayrıntılar

Çalışma alanı kalıtımı, bağımlılıkları tek bir yerde, yani çalışma alanında belirtmenize ve sonra bu çalışma alanı bağımlılıklarına paket içinden başvurmanıza izin verir. default-features belirtme biçimiyle ilgili istemeden oluşmuş bir etkileşim vardı; buna 2024 sürümünde artık izin verilmiyor.

Çalışma alanı default-features = false demediği sürece, kalıtılan paket bağımlılığında default-features = false yazmaya artık izin yoktur. Örneğin şöyle bir çalışma alanı düşünün:

[workspace.dependencies]
regex = "1.10.4"

Aşağıdaki kullanım artık hatadır:

[package]
name = "foo"
version = "1.0.0"
edition = "2024"

[dependencies]
regex = { workspace = true, default-features = false }  # HATA

Bu değişikliğin nedeni, varsayılan özellik zaten etkin olduğunda default-features = false yazmanın hiçbir etkisi olmamasına rağmen kafa karıştırmasını önlemektir.

Bir bağımlılığın varsayılan özellikleri etkinleştirip etkinleştirmemesine dair esneklik istiyorsanız, çalışma alanı tanımında default-features = false ayarladığınızdan emin olun. Yalnız birden fazla çalışma alanı üyesini aynı anda derlerseniz özelliklerin birleştirileceğini unutmayın; üyelerden biri default-features = true ayarlarsa (ya da hiç belirtmezse, çünkü varsayılan budur), bu bağımlılığı kullanan tüm üyelerde varsayılan özellikler etkin olur.

Taşıma

cargo fix --edition kullanıldığında Cargo, bu durumda Cargo.toml dosyanızdan default-features = false ifadesini otomatik olarak kaldırır.

Cargo.toml dosyanızı elle güncellemek isterseniz, derleme sırasında çıkan uyarılara bakın ve ilgili girişleri kaldırın. Önceki sürümler şöyle bir uyarı gösterir:

warning: /home/project/Cargo.toml: regex için `default-features` yok sayılıyor,
çünkü `workspace.dependencies.regex` için `default-features` belirtilmedi;
bu ileride kesin hataya dönüşebilir

Rustdoc

Aşağıdaki bölümler, 2024 sürümündeki Rustdoc değişikliklerini ayrıntılı olarak açıklar.

Rustdoc Birleşik Testleri

Özet

  • Doctest’ler artık tek bir ikili dosyada birleştirilir; bu da kayda değer bir performans iyileşmesi sağlamalıdır.

Ayrıntılar

2024 sürümünden önce rustdoc’un “test” modu, belgelerinizdeki her kod bloğunu ayrı bir çalıştırılabilir dosya olarak derliyordu. Bunu uygulamak görece basitti; ancak çok sayıda belge testi olduğunda ciddi bir performans yükü oluşturuyordu. 2024 sürümüyle birlikte rustdoc, belge testlerini tek bir ikili dosyada birleştirmeye çalışır; böylece doctest derleme ek yükü önemli ölçüde azalır.

#![allow(unused)]
fn main() {
/// Iki sayiyi toplar
///
/// ```
/// assert_eq!(add(1, 1), 2);
/// ```
pub fn add(left: u64, right: u64) -> u64 {
    left + right
}

/// Iki sayiyi cikarir
///
/// ```
/// assert_eq!(subtract(2, 1), 1);
/// ```
pub fn subtract(left: u64, right: u64) -> u64 {
    left - right
}
}

Bu örnekte iki doctest artık tek bir çalıştırılabilir dosyada derlenir. Rustdoc, özünde her örneği tek bir ikili içindeki ayrı bir fonksiyona yerleştirir. Testler yine eskisi gibi bağımsız süreçlerde çalışır; bu yüzden genel durum (örneğin global static’ler) doğru çalışmaya devam eder.1

Bu değişiklik, birleşik çalıştırılabilir yapıda çalışmayabilecek mevcut doctest’lerle olası uyumsuzlukları önlemek için yalnızca 2024 sürümünde sunulur. Yine de bu uyumsuzlukların son derece nadir olması beklenir.

standalone_crate etiketi

Bazı durumlarda rustdoc’un örnekleri tek bir çalıştırılabilir dosyada birleştirmesi mümkün değildir. Rustdoc bunun mümkün olmadığı durumları otomatik olarak tespit etmeye çalışır. Örneğin bir test aşağıdaki durumlarda diğerleriyle birleştirilmez:

  • Örneğin derlenememesi gerektiğini belirten compile_fail etiketini kullanıyorsa.
  • Örneğin hangi sürüme ait olduğunu belirten edition etiketini kullanıyorsa.2
  • Diğer testlerle çakışma çıkarabilecek global_allocator gibi genel öznitelikler kullanıyorsa.
  • Crate düzeyinde öznitelikler tanımlıyorsa (#![feature(...)] gibi).
  • $crate kullanan bir makro tanımlıyorsa; çünkü $crate yolu doğru çalışmaz.

Ancak rustdoc, bir örneğin diğer örneklerle birleştirilemeyeceği tüm durumları otomatik olarak belirleyemez. Böyle durumlarda örneğin ayrı bir çalıştırılabilir olarak derlenmesi gerektiğini belirtmek için standalone_crate dil etiketini ekleyebilirsiniz. Örneğin:

#![allow(unused)]
fn main() {
//! ```
//! let location = std::panic::Location::caller();
//! assert_eq!(location.line(), 5);
//! ```
}

Bu örnek, derleme sırasında kodun nasıl yapılandırıldığına duyarlıdır ve “birleşik” yaklaşımda çalışmaz; çünkü doctest’lerin nasıl birleştirildiğine göre satır numaraları kayar. Böyle durumlarda örneğin önceki sürümlerde olduğu gibi ayrı derlenmesini zorlamak için standalone_crate etiketini ekleyebilirsiniz:

#![allow(unused)]
fn main() {
//! ```standalone_crate
//! let location = std::panic::Location::caller();
//! assert_eq!(location.line(), 5);
//! ```
}

Taşıma

Hangi doctest’lerin standalone_crate etiketiyle işaretlenmesi gerektiğini belirleyen otomatik bir taşıma yoktur. Verilen bir doctest’in taşındığında doğru çalışmaması pek olası değildir. Önerimiz, crate’inizi 2024 sürümüne güncelleyip belge testlerini çalıştırmanız ve başarısız olan olup olmadığına bakmanızdır. Bir test başarısız olursa, ya birleşik yaklaşımla uyumlu olacak şekilde yeniden yazmanız ya da önceki davranışı korumak için standalone_crate etiketini eklemeniz gerekir.

Özellikle dikkat edilmesi ve kaçınılması gereken bazı durumlar şunlardır:

  • std::panic::Location değerlerini ya da Location kullanan şeyleri sınamak. Artık birden fazla test aynı test crate’i içinde bulunduğu için kodun konumu farklıdır.
  • std::any::type_name değerini sınamak; çünkü bunun modül yolu artık farklıdır.

  1. Bunun nasıl çalıştığının ayrıntıları için “Doctests - How were they improved?” yazısına bakın.

  2. Rustdoc’un testleri yalnızca tüm crate 2024 veya daha yeni bir sürümdeyse birleştireceğini unutmayın. Daha eski sürümlerde edition2024 etiketini kullanmak, bu testlerin birleşmesine yol açmaz.

Rustdoc İç İçe include! Değişikliği

Özet

Bir doctest include_str! ile içe alındığında, bu doctest kendi içinde ayrıca include!, include_str! veya include_bytes! kullanıyorsa yol artık Rust kaynak dosyasına göre değil, Markdown dosyasına göre çözülür.

Ayrıntılar

2024 sürümünden önce #[doc=include_str!("path/file.md")] ile belge eklemek, o dosyadaki doctest’lere span bilgisini taşımıyordu. Sonuç olarak Markdown dosyası kaynak dosyadan farklı bir dizindeyse, içe alınan yollar kaynak dosyaya göre yazılmak zorundaydı.

Örneğin şu dosyalara sahip bir kütüphane crate’i düşünün:

  • Cargo.toml
  • README.md
  • src/
    • lib.rs
  • examples/
    • data.bin

lib.rs dosyasının şu içeriğe sahip olduğunu varsayalım:

#![doc=include_str!("../README.md")]

Ve README.md dosyası da şöyle olsun:

```
let _ = include_bytes!("../examples/data.bin");
//                      ^^^ buna dikkat edin
```

2024 sürümünden önce README.md içindeki yol, lib.rs dosyasına göre yazılmalıydı. 2024 ve sonrasında ise artık README.md dosyasının kendisine göre yazılır. Bu yüzden README.md şu şekilde güncellenir:

```
let _ = include_bytes!("examples/data.bin");
```

Taşıma

Etkilenen doctest’lerdeki yolları dönüştüren otomatik bir taşıma yoktur. Doctest’lerinizden biri etkileniyorsa, yeni sürüme geçtikten sonra testleri derlerken buna benzer bir hata görürsünüz:

error: couldn't read `../examples/data.bin`: No such file or directory (os error 2)
 --> src/../README.md:2:24
  |
2 | let _ = include_bytes!("../examples/data.bin");
  |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  = note: this error originates in the macro `include_bytes` (in Nightly builds, run with -Z macro-backtrace for more info)
help: there is a file with the same name in a different directory
  |
2 | let _ = include_bytes!("examples/data.bin");
  |                        ~~~~~~~~~~~~~~~~~~~

Doctest’lerinizi Rust 2024’e taşımak için etkilenen tüm yolları, doctest’leri içeren dosyaya göre göreceli olacak şekilde güncelleyin.

Rustfmt

Aşağıdaki bölümler, 2024 sürümündeki Rustfmt değişikliklerini ayrıntılı olarak açıklar.

Rustfmt: Stil Sürümü

Özet

Kullanıcı artık rustfmt ile hangi stil sürümünün kullanılacağını denetleyebilir.

Ayrıntılar

Rustfmt’in ürettiği varsayılan biçimlendirme, Rust Stil Rehberi kuralları tarafından belirlenir.

Ayrıca Rustfmt, kullanıcıların Rust araç zincirini güncellerken gereksiz biçimlendirme karmaşası yaşamamasını hedefleyen bir biçimlendirme kararlılığı garantisine sahiptir. Bu garanti, özünde Rustfmt’in daha yeni bir sürümünün, önceki bir Rustfmt sürümünün başarıyla biçimlendirdiği çıktıyı değiştirememesi anlamına gelir.

Bu iki kısıtın birleşimi, tarihsel olarak hem Stil Rehberi’ni hem de Rustfmt’in varsayılan biçimlendirme davranışını kilitlemişti. Bu çıkmaz, stil iyileştirmeleri üzerinde yineleme yapmayı zorlaştırmak ve Rustfmt’in çoktan gereksiz hale gelmiş eski biçimlendirme tuhaflıklarını sürdürmesini zorunlu kılmak gibi sorunlara yol açtı; örneğin iç içe demet erişimi gibi.

RFC 3338, Rust Stil Rehberi’ni Rust’ın sürüm modeliyle hizalayan bir mekanizma kurarak bu çıkmazı çözdü. Böylece Stil Rehberi sürümler arasında evrilebilir ve rustfmt, kullanıcıların istedikleri Stil Rehberi sürümünü, yani Stil Sürümü’nü belirtmesine izin verir.

2024 sürümünde rustfmt, biçimlendirmede kullanılacak Stil Sürümü’nü kullanıcıların denetleyebilmesini destekler. Stil Rehberi’nin 2024 sürümü ayrıca bu Sürüm Rehberi’nin başka yerlerinde anlatılan çeşitli iyileştirmeler içerir.

Varsayılan olarak rustfmt, ayrıştırma için kullanılan standart Rust sürümüyle aynı Stil Sürümü’nü kullanır; ancak Stil Sürümü ayrıca geçersiz kılınabilir ve ayrı yapılandırılabilir.

rustfmti 2024 Stil Sürümü ile çalıştırmanın birden fazla yolu vardır:

edition alanı 2024 olan bir Cargo.toml dosyanız varsa şunu çalıştırın:

cargo fmt

Ya da ayrıştırma ve Stil Rehberi için 2024 sürümünü kullanmak üzere rustfmti doğrudan --edition 2024 ile çalıştırın:

rustfmt lib.rs --edition 2024

Stil sürümü ayrıca rustfmt.toml ya da .rustfmt.toml yapılandırma dosyasında da ayarlanabilir:

style_edition = "2024"

Bu durumda rustfmt doğrudan çalıştırıldığında bu ayar kullanılır:

rustfmt lib.rs

Alternatif olarak stil sürümü rustfmt seçenekleriyle doğrudan belirtilebilir:

rustfmt lib.rs --style-edition 2024

Taşıma

cargo fmt ya da rustfmti 2024 sürümü veya stil sürümü ile çalıştırmak, biçimlendirmeyi otomatik olarak 2024 stil sürümü biçimine taşır.

Düzenleyicilerinin kaydederken biçimlendirme özelliğini kullanabilecek katkıcıları olan projelerin, kullandıkları style_edition değerini içeren bir rustfmt.toml dosyasını projelerine eklemeleri ya da kullanıcılarını yerel düzenleyicilerindeki bu özelliği aynı style_edition ile ayarlamaya teşvik etmeleri önemle tavsiye edilir.

Bunun amacı, düzenleyicinin kaydederken ürettiği çıktının geliştiricinin elle çalıştırdığı cargo fmt ya da projenin CI sürecindeki çıktıyla tutarlı olmasını sağlamaktır. Birçok düzenleyici rustfmti doğrudan çalıştırır ve bu varsayılan olarak 2015 sürümünü kullanır; cargo fmt ise Cargo.toml içinde belirtilen sürümü kullanır.

Rustfmt: Biçimlendirme Düzeltmeleri

Özet

  • Çeşitli biçimlendirme senaryolarına yönelik düzeltmeler.

Ayrıntılar

2024 stil sürümü, çeşitli biçimlendirme senaryoları için bir dizi düzeltme getirir.

Öğelerden sonra ya da blok sonunda ilgisiz son yorumları hizalamayın

Daha önce rustfmt, son yorumlu bir öğeyi izleyen satırdaki yorumun, bu son yorumla aynı hizaya çekilmesi gerektiğini varsayıyordu. Bu davranış değiştirildi; artık bu yorumlar girintilenmiyor.

Stil sürümü 2021:

pub const IFF_MULTICAST: ::c_int = 0x0000000800; // Supports multicast
                                                 // Multicast using broadcst. add.

pub const SQ_CRETAB: u16 = 0x000e; // CREATE TABLE
pub const SQ_DRPTAB: u16 = 0x000f; // DROP TABLE
pub const SQ_CREIDX: u16 = 0x0010; // CREATE INDEX
                                   //const SQ_DRPIDX: u16 = 0x0011; // DROP INDEX
                                   //const SQ_GRANT: u16 = 0x0012;  // GRANT
                                   //const SQ_REVOKE: u16 = 0x0013; // REVOKE

fn foo() {
    let f = bar(); // Donec consequat mi. Quisque vitae dolor. Integer lobortis. Maecenas id nulla. Lorem.
                   // Id turpis. Nam posuere lectus vitae nibh. Etiam tortor orci, sagittis
                   // malesuada, rhoncus quis, hendrerit eget, libero. Quisque commodo nulla at
    let b = baz();

    let normalized = self.ctfont.all_traits().normalized_weight(); // [-1.0, 1.0]
                                                                   // YAPILACAK(emilio): Bu araligi [.01, 10.0] yapmak anlamli olabilir;
                                                                   // css-fonts-4'un [1, 1000] araligiyla hizalamak icin.
}

Stil sürümü 2024:

pub const IFF_MULTICAST: ::c_int = 0x0000000800; // Supports multicast
// Multicast using broadcst. add.

pub const SQ_CRETAB: u16 = 0x000e; // CREATE TABLE
pub const SQ_DRPTAB: u16 = 0x000f; // DROP TABLE
pub const SQ_CREIDX: u16 = 0x0010; // CREATE INDEX
//const SQ_DRPIDX: u16 = 0x0011; // DROP INDEX
//const SQ_GRANT: u16 = 0x0012;  // GRANT
//const SQ_REVOKE: u16 = 0x0013; // REVOKE

fn foo() {
    let f = bar(); // Donec consequat mi. Quisque vitae dolor. Integer lobortis. Maecenas id nulla. Lorem.
    // Id turpis. Nam posuere lectus vitae nibh. Etiam tortor orci, sagittis
    // malesuada, rhoncus quis, hendrerit eget, libero. Quisque commodo nulla at
    let b = baz();

    let normalized = self.ctfont.all_traits().normalized_weight(); // [-1.0, 1.0]
                                                                   // YAPILACAK(emilio): Bu araligi [.01, 10.0] yapmak anlamli olabilir
                                                                   // css-fonts-4'un [1, 1000] araligiyla hizalamak icin.
}

Yorumların içindeki dizgileri girintilemeyin

Daha önce rustfmt, yorumların içindeki dizgileri yanlış biçimde biçimlendirmeye çalışıyordu.

Özgün hali:

pub fn main() {
    /*   let s = String::from(
        "
hello
world
",
    ); */
}

Stil sürümü 2021:

pub fn main() {
    /*   let s = String::from(
            "
    hello
    world
    ",
        ); */
}

Stil sürümü 2024:

Özgün halinden farklı değildir.

Uzun dizgiler ifadelerin biçimlendirilmesini engellemez

Bazı durumlarda uzun dizgiler daha önce ifadenin biçimlendirilmesini engelliyordu.

Stil sürümü 2021:

fn main() {
    let value = if x == "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." { 0 } else {10};

    let x = Testing {
              foo: "long_long_long_long_long_long_long_lo_long_long_long_long_long_long__long_long_long_long_long_long_",
bar: "long_long_long_long_long_long_long_long_long_long_lo_long_long_lolong_long_long_lo_long_long_lolong_long_long_lo_long_long_lo",
};
}

Stil sürümü 2024:

fn main() {
    let value = if x
        == "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
    {
        0
    } else {
        10
    };

    let x = Testing {
        foo: "long_long_long_long_long_long_long_lo_long_long_long_long_long_long__long_long_long_long_long_long_",
        bar: "long_long_long_long_long_long_long_long_long_long_lo_long_long_lolong_long_long_lo_long_long_lolong_long_long_lo_long_long_lo",
    };
}

impl bloklarında jenerik girintisi düzeltildi

impl öğelerindeki jenerikler gereğinden fazla girintileniyordu.

Stil sürümü 2021:

impl<
        Target: FromEvent<A> + FromEvent<B>,
        A: Widget2<Ctx = C>,
        B: Widget2<Ctx = C>,
        C: for<'a> CtxFamily<'a>,
    > Widget2 for WidgetEventLifter<Target, A, B>
{
    type Ctx = C;
    type Event = Vec<Target>;
}

Stil sürümü 2024:

impl<
    Target: FromEvent<A> + FromEvent<B>,
    A: Widget2<Ctx = C>,
    B: Widget2<Ctx = C>,
    C: for<'a> CtxFamily<'a>,
> Widget2 for WidgetEventLifter<Target, A, B>
{
    type Ctx = C;
    type Event = Vec<Target>;
}

Karmaşık bir fn biçimlendirilirken doğru girintiyi kullanın

Bazı durumlarda karmaşık bir fn imzası alışılmadık bir girintiyle sonuçlanıyordu; bu durum artık düzeltildi.

Stil sürümü 2021:

fn build_sorted_static_get_entry_names(
    mut entries: Vec<(u8, &'static str)>,
) -> (impl Fn(
    AlphabeticalTraversal,
    Box<dyn dirents_sink::Sink<AlphabeticalTraversal>>,
) -> BoxFuture<'static, Result<Box<dyn dirents_sink::Sealed>, Status>>
        + Send
        + Sync
        + 'static) {
}

Stil sürümü 2024:

fn build_sorted_static_get_entry_names(
    mut entries: Vec<(u8, &'static str)>,
) -> (
    impl Fn(
        AlphabeticalTraversal,
        Box<dyn dirents_sink::Sink<AlphabeticalTraversal>>,
    ) -> BoxFuture<'static, Result<Box<dyn dirents_sink::Sealed>, Status>>
    + Send
    + Sync
    + 'static
) {
}

İç içe demet indeksleme ifadesinde fazladan boşluğu önleyin

İç içe demet indeksleme ifadeleri yanlışlıkla fazladan boşluk içeriyordu.

Stil sürümü 2021:

fn main() {
    let _ = ((1,),).0 .0;
}

Stil sürümü 2024:

fn main() {
    let _ = ((1,),).0.0;
}

match içindeki blokta yer alan return/break/continue ifadelerini noktalı virgülle bitirin

match kolundaki bir blok içinde bulunan return, break ya da continue, yanlışlıkla noktalı virgülsüz bırakılıyordu.

Stil sürümü 2021:

fn foo() {
    match 0 {
        0 => {
            return AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
        }
        _ => "",
    };
}

Stil sürümü 2024:

fn foo() {
    match 0 {
        0 => {
            return AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA;
        }
        _ => "",
    };
}

Uzun dizi ve dilim desenleri artık satır kaydırır

Uzun dizi ve dilim desenleri doğru biçimde satır kaydırmıyordu.

Stil sürümü 2021:

fn main() {
    let [aaaaaaaaaaaaaaaaaaaaaaaaaa, bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, cccccccccccccccccccccccccc, ddddddddddddddddddddddddd] =
        panic!();
}

Stil sürümü 2024:

fn main() {
    let [
        aaaaaaaaaaaaaaaaaaaaaaaaaa,
        bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb,
        cccccccccccccccccccccccccc,
        ddddddddddddddddddddddddd,
    ] = panic!();
}

Son ifade deyimini ifade olarak biçimlendirin

Bir bloktaki son deyim bir ifade ise artık ifade olarak biçimlendirilir.

Stil sürümü 2021:

fn main() {
    let toto = || {
        if true {
            42
        } else {
            24
        }
    };

    {
        T
    }
}

Stil sürümü 2024:

fn main() {
    let toto = || {
        if true { 42 } else { 24 }
    };

    { T }
}

Fonksiyon ve makro çağrıları arasında aynı biçimlendirme

Bazı biçimlendirmeler artık makro çağrısında da fonksiyon çağrısındakiyle aynıdır.

Stil sürümü 2021:

fn main() {
    macro_call!(HAYSTACK
        .par_iter()
        .find_any(|&&x| x[0] % 1000 == 999)
        .is_some());

    fn_call(
        HAYSTACK
            .par_iter()
            .find_any(|&&x| x[0] % 1000 == 999)
            .is_some(),
    );
}

Stil sürümü 2024:

fn main() {
    macro_call!(
        HAYSTACK
            .par_iter()
            .find_any(|&&x| x[0] % 1000 == 999)
            .is_some()
    );

    fn_call(
        HAYSTACK
            .par_iter()
            .find_any(|&&x| x[0] % 1000 == 999)
            .is_some(),
    );
}

Gövdesi tek bir döngü olan kapanışlarda blok biçimini zorunlu kılın

Tek bir döngü içeren kapanışlar artık blok ifadesi olarak biçimlendirilir.

Stil sürümü 2021:

fn main() {
    thread::spawn(|| loop {
        println!("yineleme");
    });
}

Stil sürümü 2024:

fn main() {
    thread::spawn(|| {
        loop {
            println!("yineleme");
        }
    });
}

where cümleciklerindeki boş satırlar artık kaldırılır

where cümleciği içindeki boş satırlar artık kaldırılır.

Stil sürümü 2021:

fn foo<T>(_: T)
where
    T: std::fmt::Debug,

    T: std::fmt::Display,
{
}

Stil sürümü 2024:

fn foo<T>(_: T)
where
    T: std::fmt::Debug,
    T: std::fmt::Display,
{
}

Öznitelikli let-else deyiminin biçimlendirmesi düzeltildi

Bir let-else deyiminde öznitelik varsa, bu durum else kısmının yanlış şekilde ayrı satıra sarılmasına neden oluyordu.

Stil sürümü 2021:

fn main() {
    #[cfg(target_os = "linux")]
    let x = 42
    else {
        todo!()
    };

    // Bu, öznitelik olmadan da aynıdır.
    let x = 42 else { todo!() };
}

Stil sürümü 2024:

fn main() {
    #[cfg(target_os = "linux")]
    let x = 42 else { todo!() };

    // Bu, öznitelik olmadan da aynıdır.
    let x = 42 else { todo!() };
}

Enum varyantı belge yorumlarını kaydırırken bir sütun kayma hatası

wrap_comments özelliği kullanıldığında yorumlar bir sütun kaymalı biçimde satır kaydırıyordu.

Özgün hali:

pub enum Severity {
    /// Ama burada bu yorum 120 sutun genisliginde ve bicimlendirici hala bunu iki ayri satira bolmek istiyor.
    Error,
    /// Bu yorum 119 sutun genisliginde ve kusursuz calisiyor. Ornek metin. ornek metin. ornek metin. ornek metin ornek.
    Warning,
}

Stil sürümü 2021:

pub enum Severity {
    /// Ama burada bu yorum 120 sutun genisliginde ve bicimlendirici bunu hala iki ayri satira
    /// bolmek istiyor.
    Error,
    /// Bu yorum 119 sutun genisliginde ve kusursuz calisiyor. Ornek metin. ornek metin. ornek metin. ornek metin ornek.
    Warning,
}

Stil sürümü 2024:

pub enum Severity {
    /// Ama burada bu yorum 120 sutun genisliginde ve bicimlendirici hala bunu iki ayri satira bolmek istiyor.
    Error,
    /// Bu yorum 119 sutun genisliginde ve kusursuz calisiyor. Ornek metin. ornek metin. ornek metin. ornek metin ornek.
    Warning,
}

format_macro_matchers için bir sütun kayma hatası

format_macro_matchers özelliği kullanıldığında eşleştirici bir sütun kaymalı biçimde satır kaydırıyordu.

Stil sürümü 2021:

macro_rules! test {
    ($aasdfghj:expr, $qwertyuiop:expr, $zxcvbnmasdfghjkl:expr, $aeiouaeiouaeio:expr, $add:expr) => {{
        return;
    }};
}

Stil sürümü 2024:

macro_rules! test {
    (
        $aasdfghj:expr, $qwertyuiop:expr, $zxcvbnmasdfghjkl:expr, $aeiouaeiouaeio:expr, $add:expr
    ) => {{
        return;
    }};
}

match => sonrasındaki yorumda => bulununca oluşan hata düzeltildi

Bazı durumlarda bir match ifadesindeki => sonrasında gelen yorumun içinde yeniden => geçmesi, doğru biçimlendirmeyi bozuyordu.

Stil sürümü 2021:

fn main() {
    match a {
        _ =>
        // => iceren yorum
                {
            println!("A")
        }
    }
}

Stil sürümü 2024:

fn main() {
    match a {
        _ =>
        // => iceren yorum
        {
            println!("A")
        }
    }
}

match ifadesindeki birden çok iç öznitelik yanlış girintileniyordu

Bir match ifadesi içindeki birden çok iç öznitelik yanlış girintileniyordu.

Stil sürümü 2021:

pub fn main() {
    match a {
        #![attr1]
    #![attr2]
        _ => None,
    }
}

Stil sürümü 2024:

pub fn main() {
    match a {
        #![attr1]
        #![attr2]
        _ => None,
    }
}

Taşıma

Bu değişiklik, cargo fmt ya da rustfmti 2024 sürümüyle çalıştırarak otomatik olarak uygulanabilir. Taşıma ve stil sürümlerinin nasıl çalıştığı hakkında daha fazla bilgi için Stil sürümü bölümüne bakın.

Rustfmt: Ham Tanımlayıcı Sıralaması

Özet

rustfmt artık ham tanımlayıcıları doğru biçimde sıralar.

Ayrıntılar

Rust Stil Rehberi, rustfmtin örneğin use bildirimlerinde uyguladığı sıralama kurallarını içerir.

2024 sürümünden önce rustfmt sıralama yaparken tanımlayıcının kendisi yerine başındaki r# token’ını dikkate alıyordu; bu da istenmeyen sonuçlara yol açıyordu. Örneğin:

use websocket::client::ClientBuilder;
use websocket::r#async::futures::Stream;
use websocket::result::WebSocketError;

2024 sürümünde rustfmt artık şunu üretir:

use websocket::r#async::futures::Stream;
use websocket::client::ClientBuilder;
use websocket::result::WebSocketError;

Taşıma

Bu değişiklik, cargo fmt ya da rustfmti 2024 sürümüyle çalıştırarak otomatik olarak uygulanabilir. Taşıma ve stil sürümlerinin nasıl çalıştığı hakkında daha fazla bilgi için Stil sürümü bölümüne bakın.

Rustfmt: Sürüm Sıralaması

Özet

rustfmt yeni bir sıralama algoritması kullanır.

Ayrıntılar

Rust Stil Rehberi, rustfmtin örneğin use bildirimleri gibi çeşitli bağlamlarda uyguladığı sıralama kurallarını içerir.

Stil Rehberi’nin ve Rustfmt’in önceki sürümleri genellikle “ASCIIbetical” temelli bir yaklaşım kullanıyordu. 2024 sürümünde bu, Unicode karakterlerini sözlüksel olarak karşılaştıran ve ASCII rakam karşılaştırmalarında daha iyi sonuç veren sürüm sıralaması benzeri bir algoritmaya dönüştürüldü.

Örneğin şu sıralanmamış girdi verilsin:

use std::num::{NonZeroU32, NonZeroU16, NonZeroU8, NonZeroU64};
use std::io::{Write, Read, stdout, self};

Önceki sürümlerde rustfmt şu çıktıyı üretirdi:

use std::io::{self, stdout, Read, Write};
use std::num::{NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8};

2024 sürümünde rustfmt artık şu çıktıyı üretir:

use std::io::{self, Read, Write, stdout};
use std::num::{NonZeroU8, NonZeroU16, NonZeroU32, NonZeroU64};

Taşıma

Bu değişiklik, cargo fmt ya da rustfmti 2024 sürümüyle çalıştırarak otomatik olarak uygulanabilir. Taşıma ve stil sürümlerinin nasıl çalıştığı hakkında daha fazla bilgi için Stil sürümü bölümüne bakın.