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:
- Bagimliliklarinizi en guncel surumlere yuklemek icin
cargo updatecalistirin. cargo fix --editioncalistirin.Cargo.tomldosyasini duzenleyipeditionalanini bir sonraki surume ayarlayin; orneginedition = "2024".- Duzeltmelerin ise yaradigini dogrulamak icin
cargo buildveyacargo testcalistirin. - Projeyi yeniden bicimlendirmek icin
cargo fmtcalistirin.
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:
- En guncel nightly surumu kurun:
rustup update nightly. cargo +nightly fix --editioncalistirin.Cargo.tomldosyasini duzenleyip en uste ([package]bolumunun ustune)cargo-features = ["edition20xx"]ekleyin veeditionalanini gecmek istediginiz surumu gosterecek sekildeedition = "20xx"yapin.- Yeni surumde calistigini dogrulamak icin
cargo +nightly checkcalistirin.
⚠ Dikkat: Bir sonraki surumde uygulanacak ozellikler icin
cargo fixtarafinda 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.
rustfixkutuphanesini 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 icinexamplesdizinine 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:
- Surum 2018:
- Surum 2021 icin herhangi bir deyim lint’i yoktur.
Asagidaki yonlendirmeler, ancak birkac derleyici/Cargo hatasiyla ugrasmayi goze alan kisilere onerilir. Sorun cikarsa, mumkun olan en fazla ilerlemeyi saglamak icin yukarida anlatilan
--broken-codesecenegini 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
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
Özet
usebildirimlerindeki 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ıkcrate,selfveyasuperile 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.crateanahtar 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.rsveornek/alt dizini aynı anda var olabilir; alt modülleri bir alt dizine yerleştirirken artıkmod.rsgerekmez.usebildirimlerindeki 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çestdotomatik olarak içe aktarılır.core: Genelde gerekli değildir; çünkü crate#![no_core]ile işaretlenmedikçecoreotomatik 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 veyarustcye uygun--externbayraklarını geçmeyen başka bir derleme aracı kullanıyorsanızextern crate proc_macro;gerekebilir.alloc:alloccrate’indeki öğelere geneldestdcrate’indeki yeniden dışa aktarmalar üzerinden erişilir. Eğer bellek ayırmayı destekleyen birno_stdcrate’i ile çalışıyorsanızalloccrate’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ı
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ı
Özet
- Trait fonksiyon parametreleri, fonksiyonun gövdesi olduğunda herhangi bir çürütülemez desen kullanabilir.
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
Özet
dyn, katı anahtar kelime oldu; 2015’te ise zayıf anahtar kelime olarak davranıyordu.asyncveawait, katı anahtar kelimelerdir.try, ayrılmış bir anahtar kelimedir.
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&Traitve&mut Trait,&dyn Traitile&mut dyn Traitolur
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
tyvar_behind_raw_pointerlint’i artık kesin hatadır.
Ayrıntılar
Ayrıntılar için Rust konusu #46906’ya bakın.
Cargo Değişiklikleri
Özet
- Bir
Cargo.tomlmanifest dosyasında hedef tanımı varsa, bu artık diğer hedeflerin otomatik keşfini kendiliğinden devre dışı bırakmaz. pathalanı ayarlanmamış hedefler içinsrc/{target_name}.rsbiçimindeki hedef yolları artık otomatik olarak çıkarsanmaz.- Geçerli dizin için doğrudan
cargo installkullanımına artık izin yoktur; mevcut paketi kurmak içincargo install --path .yazmanız gerekir.
Rust 2021
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,TryFromveFromIteratortrait’leri artık prelude’un bir parçasıdır.- Bu durum, trait metod çağrılarını belirsiz hale getirerek bazı kodların derlenememesine yol açabilir.
Ayrıntılar
Standart kütüphanenin prelude’u,
her modülde otomatik olarak içe aktarılan her şeyi barındıran modüldür.
Option, Vec, drop ve Clone gibi sık kullanılan öğeleri içerir.
Rust derleyicisi, prelude’a yapılan eklemelerin mevcut kodu bozmamasını sağlamak
için elle içe aktarılan öğelere prelude’dan gelenlerden daha yüksek öncelik verir.
Örneğin ornek adlı bir crate ya da modülünüz varsa ve içinde pub struct Option;
tanımlanmışsa, use ornek::*; ifadesi Option adını tartışmasız biçimde
ornek içindeki tipe bağlar; standart kütüphanedekine değil.
Ancak prelude’a bir trait eklemek, mevcut kodu daha ince bir şekilde bozabilir.
Örneğin BenimTryInto trait’inden gelen x.try_into() çağrısı, std içindeki
TryInto da içe aktarıldığında derlenmeyebilir. Çünkü bu kez try_into
çağrısının hangi trait’ten geldiği belirsizleşir. TryIntoyu şimdiye kadar
prelude’a eklememiş olmamızın nedeni de buydu; aksi halde çok sayıda kod kırılırdı.
Çözüm olarak Rust 2021 yeni bir prelude kullanır. Bu yeni sürüm, üç ek dışında mevcut prelude ile aynıdır:
Takip konusu burada bulunabilir.
Taşıma
2021 sürümünün bir parçası olarak, Rust 2018 kod tabanlarının Rust 2021’e
otomatik taşınmasına yardımcı olmak için rust_2021_prelude_collisions
taşıma lint’i eklendi.
Kodunuzu Rust 2021 ile uyumlu hale getirmek için şunu çalıştırın:
cargo fix --edition
Bu lint, yeni prelude trait’lerinden birindeki metodlarla aynı ada sahip fonksiyon ya da metod çağrılarını tespit eder. Bazı durumlarda, önceden çağırdığınız aynı işlevin çağrılmaya devam etmesini sağlamak için çağrıları yeniden yazabilir.
Kodunuzu elle taşımak ya da cargo fixin ne yaptığını daha iyi anlamak
istiyorsanız, aşağıda taşımanın gerekli olduğu ve olmadığı durumları özetledik.
Taşıma gerekli
Çakışan trait metodları
Kapsamda bulunan iki trait aynı metod adına sahipse, hangi metodun kullanılacağı belirsiz olur. Örneğin:
trait BenimTrait<A> {
// Bu ad, `std` içindeki `FromIterator` trait'inin `from_iter` metoduyla aynıdır.
fn from_iter(x: Option<A>);
}
impl<T> BenimTrait<()> for Vec<T> {
fn from_iter(_: Option<()>) {}
}
fn main() {
// Vec<T>, hem `std::iter::FromIterator` hem de `BenimTrait` uygular
// Eğer iki trait de kapsamdaysa (Rust 2021'de olduğu gibi),
// hangi `from_iter` metodunun çağrılacağı belirsizleşir
<Vec<i32>>::from_iter(None);
}
Bunu tam nitelikli sözdizimiyle düzeltebiliriz:
fn main() {
// Artık hangi trait metoduna başvurduğumuz açık
<Vec<i32> as BenimTrait<()>>::from_iter(None);
}
dyn Trait nesneleri üzerindeki doğal metodlar
Bazı kullanıcılar, metod adı yeni bir prelude trait’i ile çakışan bir
dyn Trait değeri üzerinde metod çağırır:
#![allow(unused)]
fn main() {
mod alt_modul {
pub trait BenimTrait {
// Bu, `TryInto::try_into` ile aynı ada sahiptir
fn try_into(&self) -> Result<u32, ()>;
}
}
// `BenimTrait` burada kapsamda değildir ve yalnızca `alt_modul::BenimTrait` yoluyla anılabilir
fn islem(f: Box<dyn alt_modul::BenimTrait>) {
// Eğer `std::convert::TryInto` kapsamdaysa (Rust 2021'de olduğu gibi),
// hangi `try_into` metodunun çağrılacağı belirsizleşir
f.try_into();
}
}
Statik gönderim metodlarının aksine, bir trait nesnesi üzerinde trait metod
çağırmak için trait’in kapsamda olması gerekmez. Yukarıdaki kod, çakışan adlı
başka bir trait kapsamda olmadığı sürece çalışır. TryInto trait’i kapsamda
olduğunda ise belirsizlik doğar: Çağrı BenimTrait::try_into mu olmalı,
yoksa std::convert::TryInto::try_into mu?
Bu gibi durumlarda ek dereference ekleyerek veya metod alıcısının türünü daha
açık hale getirerek sorunu çözebiliriz. Böylece prelude trait’indeki metodlar
yerine dyn Trait üzerindeki metod seçilir. Örneğin yukarıdaki f.try_into()
çağrısını (&*f).try_into() biçimine çevirmek, çağrının yalnızca
BenimTrait::try_into metoduna gideceğini garantiler.
Taşıma gerekmiyor
Doğal metodlar
Birçok tür, trait metodlarıyla aynı ada sahip kendi doğal metodlarını tanımlar.
Örneğin aşağıda BenimYapi yapısı, standart kütüphanedeki FromIterator
trait’inin metoduyla aynı ada sahip from_iter metodunu uygular:
#![allow(unused)]
fn main() {
use std::iter::IntoIterator;
struct BenimYapi {
data: Vec<u32>
}
impl BenimYapi {
// Bu, `std::iter::FromIterator::from_iter` ile aynı ada sahiptir
fn from_iter(iter: impl IntoIterator<Item = u32>) -> Self {
Self {
data: iter.into_iter().collect()
}
}
}
impl std::iter::FromIterator<u32> for BenimYapi {
fn from_iter<I: IntoIterator<Item = u32>>(iter: I) -> Self {
Self {
data: iter.into_iter().collect()
}
}
}
}
Doğal metodlar her zaman trait metodlarından önce gelir; bu yüzden burada bir taşıma gerekmez.
Uygulama Notu
Lint, 2021 sürümünü bir kod tabanına getirmenin ad çözümleme çakışmasına yol açıp açmayacağını belirlerken birkaç etkeni hesaba katmak zorundadır. Bu etkenler şunlardır:
- Çağrı bir tam nitelikli çağrı mı, yoksa nokta ile metod çağrısı sözdizimi
mi kullanıyor?
Nokta ile metod çağrısı sözdiziminde otomatik referans ve otomatik
dereference devreye girdiği için bu durum ad çözümlemesini etkiler. Elle
referans alma ya da dereference yapma, nokta sözdiziminde önceliği açıkça
belirlemeye yardım eder. Tam nitelikli çağrıda ise metod yolunda tür ve
trait adı açıkça yazılmalıdır; örneğin
<Tur as Trait>::metod. - Bu bir doğal metod mu, yoksa trait metodu mu?
selfalan doğal metodlar, trait metodlarından önce geldiği içinTryInto::try_intoçağrısına göre öncelik kazanır. Ancak&selfya da&mut selfalan doğal metodlar, otomatik referans gerektirdikleri için aynı önceliği kazanmaz;TryInto::try_intoiseselfaldığı için buna ihtiyaç duymaz. - Bu metodun kaynağı
coreya dastdmi? Trait’ler kendi kendileriyle çakışamayacağı için bu önemlidir. - Verilen tür, çakışma yaşayabileceği trait’i gerçekten uyguluyor mu?
- Metod dinamik gönderim üzerinden mi çağrılıyor?
Yani
selftürüdyn Traitmi? Böyleyse, trait içe aktarmaları çözümlemeyi etkilemez ve taşıma lint’inin devreye girmesi gerekmez.
Varsayılan Cargo Özellik Çözücüsü
Özet
Cargo.tomliçindekiedition = "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
IntoIteratoruygular. - 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üzdenarray.into_iter(), eskiden olduğu gibi hâlâ(&array).into_iter()olarak çözülür. array.into_iter(), Rust 2021’de doğrudanIntoIterator::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ıkayerine yalnızcaa.xdeğerini yakalar.- Bu durum, değerlerin farklı zamanlarda düşmesine neden olabilir ya da
kapanışların
SendveyaClonegibi 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çinlet _ = &abenzeri ifadeler ekler.
- Olası değişiklikler tespit edilirse
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:
Clone: yakalanan tüm değerlerCloneise.- otomatik trait’ler olan
Send,SyncveUnwindSafe: yakalanan tüm değerler ilgili trait’i uyguluyorsa.
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ıkprintln!()gibi her zamanformat_args!(..)kullanır.{karakterini{{diye kaçırmadanpanic!("{")yazmak artık kabul edilmez.xbir dizgi sabiti değilsepanic!(x)artık kabul edilmez.- Dizgi olmayan bir yükle panic üretmek için
std::panic::panic_any(x)kullanın. - Ya da
xinDisplayuygulamasını kullanmak içinpanic!("{}", x)yazın.
- Dizgi olmayan bir yükle panic üretmek için
- 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ğinasync, 2015 sürümünde anahtar kelime değilkenk#asyncbiçimi,asyncsö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ğinf"hello {name}", eşdeğerformat!()çağrısının kısa yazımı olabilir. -
s"":Stringsabitleri 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_keywordartık bir ömür olarak kullanılabilir; böylece'r#fngibi 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_objectsveellipsis_inclusive_range_patternslint’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_rulesmakroları içinde çalışma biçimi biraz değişiyor:macro_rulesiçindeki$_:patartık|kullanımını da eşleştirir; örneğinA | B.- Yeni
$_:pat_param, eskiden$_:patnasıl davranıyorsa öyle davranır; üst düzey|ile eşleşmez. $_:pat_paramtü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 dacr"foo"biçimindeki sabitler,&core::ffi::CStrtü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
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. Captureshilesi (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,$exprdeğerlendirilirken oluşan geçici değerler artıkelsedalına girdikten sonra değil, programaelsedalı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)]
}
if ve while İçinde let Zincirleri
Özet
ifvewhilekoşul işlenenlerindeletifadelerinin 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)]
}
Match Ergonomisi Çekinceleri
Özet
- Bir bağ üzerinde
mut,refya daref mutyazmak 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
movedeğilse bağ üzerindemut,refya daref mutyazmak hatadır.
- Başka bir deyişle, varsayılan bağlama modu
- 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
moveiken scrutinee içindeki referanslarla eşleşebilir.
- Başka bir deyişle, referans desenleri yalnızca varsayılan bağlama modu
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
- [
externblokları] artıkunsafeanahtar 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)]
}
Güvensiz Öznitelikler
Özet
- Aşağıdaki öznitelikler artık
unsafeolarak işaretlenmelidir:
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)]
}
unsafe_op_in_unsafe_fn Uyarısı
Özet
unsafe_op_in_unsafe_fnlint’i artık varsayılan olarak uyarı verir. Bu uyarı, unsafe fonksiyonların içinde açıkunsafeblok 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_refslint düzeyi artık varsayılan olarakdenydir. Bu lint, birstatic mutiç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_unsafelint’i artık varsayılan olarakdenydir.
Ayrıntılar
Derleyici, bir [zorlama noktasında][] ! (never) türünde bir değer gördüğünde,
tür denetleyicisinin herhangi bir türü çıkarabilmesine izin vermek için örtük
bir zorlama ekler:
#![allow(unused)]
fn main() {
#![feature(never_type)]
// Bu:
let x: u8 = panic!();
// ...derleyici tarafından özünde şuna dönüştürülür:
let x: u8 = absurd(panic!());
// ...buradaki `absurd` aşağıdaki fonksiyondur
// (`!` her zaman ulaşılamaz kodu işaretlediği için bu güvenlidir):
fn absurd<T>(x: !) -> T { x }
}
Tür çıkarılamıyorsa bu durum derleme hatalarına yol açabilir:
#![allow(unused)]
fn main() {
#![feature(never_type)]
fn absurd<T>(x: !) -> T { x }
// Bu:
{ panic!() };
// ...şuna dönüştürülür:
{ absurd(panic!()) }; //~ HATA `absurd` icin tur cikarilamiyor
}
Bu tür hataları önlemek için derleyici, absurd çağrılarını nereye eklediğini
hatırlar ve türü çıkaramazsa bunun yerine geri dönüş türünü kullanır:
#![allow(unused)]
fn main() {
#![feature(never_type)]
fn absurd<T>(x: !) -> T { x }
type Fallback = /* Keyfi seçilmiş bir tür! */ !;
{ absurd::<Fallback>(panic!()) }
}
Buna “never türü geri dönüşü” denir.
Tarihsel olarak geri dönüş türü () idi. Bu, geri dönüş mekanizması olmasa
derleyicinin () çıkaramayacağı durumlarda bile ! türünün kendiliğinden ()
türüne zorlanmasına neden oluyordu. Bu kafa karıştırıcıydı ve ! türünün
kararlı hale gelmesini engelliyordu.
2024 sürümünde geri dönüş türü artık ! oldu. Bu değişikliği daha sonra tüm
sürümlere yaymayı planlıyoruz. Böylece davranış daha sezgisel hale gelir.
Artık ! verdiğinizde ve onu başka bir şeye zorlamak için neden yoksa, değer
! olarak kalır.
Bazı durumlarda kodunuz geri dönüş türünün () olmasına dayanıyor olabilir;
bu yüzden bu değişiklik derleme hatalarına ya da davranış değişikliklerine yol açabilir.
never_type_fallback_flowing_into_unsafe
never_type_fallback_flowing_into_unsafe lint’inin varsayılan düzeyi 2024
sürümünde warndan denyye yükseltildi. Bu lint, ! geri dönüşü ile unsafe
kod arasındaki ve tanımsız davranışa yol açabilecek belirli bir etkileşimi
tespit etmeye yardım eder. Tam açıklama için bağlantıya bakın.
Taşıma
Otomatik bir düzeltme yoktur; ancak sürüm değişikliğiyle bozulacak kod otomatik olarak tespit edilir. Önceki sürümdeyken bile kodunuz bozulacaksa uyarı görürsünüz.
Çözüm, geri dönüş türü kullanılmasın diye türü açıkça belirtmektir. Ne yazık ki hangi türün yazılması gerektiğini görmek her zaman kolay olmayabilir.
Bu değişiklikle bozulan en yaygın kalıplardan biri, f fonksiyonu dönüş
türünün Ok kısmı üzerinde jenerikken f()?; kullanımıdır:
#![allow(unused)]
fn main() {
#![allow(dependency_on_unit_never_type_fallback)]
fn outer<T>(x: T) -> Result<T, ()> {
fn f<T: Default>() -> Result<T, ()> {
Ok(T::default())
}
f()?;
Ok(x)
}
}
Bu örnekte T türünün çıkarılamayacağını düşünebilirsiniz. Ancak ? işlecin
şu anki sözdizimsel açılımı nedeniyle tür önceden () olarak çıkarılıyordu;
artık ! olarak çıkarılacak.
Sorunu çözmek için T türünü açıkça belirtmelisiniz:
#![allow(unused)]
fn main() {
#![deny(dependency_on_unit_never_type_fallback)]
fn outer<T>(x: T) -> Result<T, ()> {
fn f<T: Default>() -> Result<T, ()> {
Ok(T::default())
}
f::<()>()?;
// ...ya da:
() = f()?;
Ok(x)
}
}
Bir diğer görece yaygın durum, kapanış içinde panic üretmektir:
#![allow(unused)]
fn main() {
#![allow(dependency_on_unit_never_type_fallback)]
trait Unit {}
impl Unit for () {}
fn run<R: Unit>(f: impl FnOnce() -> R) {
f();
}
run(|| panic!());
}
Daha önce panic! içinden gelen !, Unit uygulayan () türüne zorlanıyordu.
Artık ! olduğu gibi kaldığı için bu kod, ! Unit uygulamadığından
başarısız olur. Bunu çözmek için kapanışın dönüş türünü açıkça belirtebilirsiniz:
#![allow(unused)]
fn main() {
#![deny(dependency_on_unit_never_type_fallback)]
trait Unit {}
impl Unit for () {}
fn run<R: Unit>(f: impl FnOnce() -> R) {
f();
}
run(|| -> () { panic!() });
}
f()? örneğine benzer bir durum, bir dalda ! türünde ifade ve diğer dalda
dönüş türü kısıtlanmamış bir fonksiyon kullanıldığında da görülür:
#![allow(unused)]
fn main() {
#![allow(dependency_on_unit_never_type_fallback)]
if true {
Default::default()
} else {
return
};
}
Önceden return içinden gelen ! yanlış şekilde () türüne zorlandığı için
Default::default() dönüş türü olarak () çıkarılıyordu. Artık bunun yerine
! çıkarılacak; bu yüzden ! Default uygulamadığından kod derlenmeyecektir.
Yine çözüm, türü açıkça belirtmektir:
#![allow(unused)]
fn main() {
#![deny(dependency_on_unit_never_type_fallback)]
() = if true {
Default::default()
} else {
return
};
// ...ya da:
if true {
<() as Default>::default()
} else {
return
};
}
Makro Parçacık Belirteçleri
Özet
exprparçacık belirteci artıkconstve_ifadelerini de destekler.- Geriye dönük uyumluluk için
expr_2021parç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_specifierlint’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
FutureveIntoFuturetrait’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
IntoIteratoruygular. - 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üzdenboxed_slice.into_iter(), eskiden olduğu gibi hâlâ(&(*boxed_slice)).into_iter()olarak çözülür. boxed_slice.into_iter(), Rust 2024’teIntoIterator::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
- Aşağıdaki işlevler artık
unsafeolarak işaretlenmiştir:
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.tomliçindekiedition = "2024"ayarı, Rust sürümünü bilen bir bağımlılık çözücüsünü etkinleştirenresolver = "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.tomliç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_featureskaldırıldı; yerinedefault-featureskullanın.crate_typekaldırıldı; yerinecrate-typekullanın.proc_macrokaldırıldı; yerineproc-macrokullanın.dev_dependencieskaldırıldı; yerinedev-dependencieskullanın.build_dependencieskaldırıldı; yerinebuild-dependencieskullanı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 = truebelirtiyorsa (ya dadefault-featureshiç belirtmiyorsa), kalıtılan çalışma alanı bağımlılığındadefault-features = falseartı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_failetiketini kullanıyorsa. - Örneğin hangi sürüme ait olduğunu belirten
editionetiketini kullanıyorsa.2 - Diğer testlerle çakışma çıkarabilecek
global_allocatorgibi genel öznitelikler kullanıyorsa. - Crate düzeyinde öznitelikler tanımlıyorsa (
#![feature(...)]gibi). $cratekullanan bir makro tanımlıyorsa; çünkü$crateyolu 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::Locationdeğerlerini ya daLocationkullanan ş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_namedeğerini sınamak; çünkü bunun modül yolu artık farklıdır.
-
Bunun nasıl çalıştığının ayrıntıları için “Doctests - How were they improved?” yazısına bakın. ↩
-
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
edition2024etiketini 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.tomlREADME.mdsrc/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.