A Rust kiadási ciklusa szépen kiszámítható: hathetente jön egy új stabil verzió, és az 1.67 sem hozott ebben törést. Bár azóta már az 1.68 is megjelent, érdemes visszatekerni és alaposan átvenni, mi is változott az 1.67-ben, mert a benne lévő módosítások – különösen a csatornák átírása – hosszú távon minden Rust projektre hatással lehetnek.
A #[must_use] attribútum most már async fn-eken is működik
Ez egy apróságnak hangzó, de a gyakorlatban rendkívül hasznos változás. A #[must_use] attribútumot régóta használjuk arra, hogy jelezzük a fordítónak: egy függvény visszatérési értékét nem szabad figyelmen kívül hagyni. Klasszikus példa erre a Result, amit ha eldobunk, a compiler warningot dob.
A gond az volt, hogy async függvényeken korábban nem tudtuk kihasználni ezt a mechanizmust úgy, ahogy szerettük volna – a Future-ök „lusták”, azaz amíg nem pollozzuk (jellemzően .await-tel) őket, semmi nem történik. Ha valaki elfelejtett .await-elni egy async függvényt, az gyakran csendes, nehezen észrevehető hibákhoz vezetett.
Az 1.67-től kezdve a #[must_use] attribútum async fn-eken is figyelmeztetést generál, ha a visszaadott Future-t nem használjuk fel:
#[must_use]
async fn fetch_value() -> i32 {
42
}
async fn example() {
fetch_value(); // figyelmeztetés: unused `Future` that must be used
let value = fetch_value().await;
println!("{value}");
}
Ha saját async API-t írsz, és van olyan függvényed, amelynek mellékhatása csak akkor érvényesül, ha valóban lefut (pl. adatbázis-tranzakció commit), mostantól érdemes rátenni a #[must_use]-t. Így a fordító segít elkapni azokat az eseteket, amikor valaki elfelejtette .await-elni a hívást.
Teljesen újraírt std::sync::mpsc
A legnagyobb, és talán legkevésbé látványos, de a legtöbb kódbázisra kiható változás az std::sync::mpsc csatorna implementációjának teljes újraírása. Az mpsc (multi-producer, single-consumer) csatorna a Rust standard könyvtár egyik legrégebbi és legtöbbet használt konkurenciai primitívje – gondolj bele, hány szálkezelő kódban találkoztál már channel() vagy sync_channel() hívással.
A korábbi implementáció évek óta ismert problémákkal küzdött: bizonyos ritka race condition-ök esetén előfordulhatott, hogy egy recv() hívás soha nem tér vissza, vagy hogy a csatorna teljesítménye erősen ingadozott terhelés alatt. Az új implementáció ezeket a problémákat orvosolja, és emellett jelentős sebességnövekedést is hoz – a Rust csapat mérései szerint bizonyos munkaterhelések alatt akár többszörös throughput javulás is elérhető.
A jó hír, hogy az API teljesen kompatibilis maradt, tehát a meglévő kódod változatlanul fog fordulni és futni – csak gyorsabban és megbízhatóbban.
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
for i in 0..4 {
let tx = tx.clone();
thread::spawn(move || {
tx.send(format!("üzenet a {i}. szálról")).unwrap();
});
}
drop(tx); // ha az eredeti tx-et is elengedjük, a csatorna lezárul, amint minden klón elfogy
for received in rx {
println!("kapott: {received}");
}
}
Az újraírás mögött hosszú munka áll: a régi implementációban a Receiver és a Sender közötti szinkronizáció egy bonyolult, kézzel írt lock-free algoritmusra épült, amely nehezen volt karbantartható. Az új verzió egyszerűbb, jobban tesztelhető belső architektúrát használ, ami egyszerre javítja a megbízhatóságot és a teljesítményt.
Miért fontos ez neked?
Ha a projektedben szálak közötti kommunikációra std::sync::mpsc-t használsz – akár közvetlenül, akár egy köztes crate-en keresztül –, az 1.67-re (vagy újabbra) váltással automatikusan profitálsz a javításokból, kód módosítása nélkül. Ez különösen jó hír azoknak, akik erősen terhelt, sok-producer/egy-consumer mintázatú rendszereket futtatnak, például worker pool-okat vagy esemény-aggregátorokat.
Ha korábban észrevettél furcsa, nehezen reprodukálható „beragadásokat” egy mpsc::Receiver::recv() hívásnál nagy terhelés alatt, most különösen érdemes frissíteni és megnézni, eltűnik-e a jelenség.
Egyéb apró nyelvi és könyvtári változások
Az 1.67 mellett persze rengeteg kisebb finomítás is becsatlakozott, amelyek önmagukban nem indokolnának külön hírt, de együtt szépen mutatják, hogy a nyelv folyamatosan érik:
- Kisebb API-stabilizációk a
coreésstdkönyvtárakban, például újabb konstans metódusok a numerikus típusokon. - Diagnosztikai üzenetek további finomítása – a fordító hibaüzenetei apránként egyre pontosabbak és emberibbek lettek.
- A RISC-V 64-bit platform támogatásának további erősítése a build infrastruktúrában.
Érdemes megjegyezni, hogy ezek a változások nem önmagukban az 1.67 részei, de jó alkalom felidézni, hogy az elmúlt release-ekben stabilizálódott néhány nagyobb nyelvi feature is aktívan használható már – ha még nem próbáltad ki, most van itt az idő.
A GATs (Generic Associated Types) lehetővé teszi, hogy egy trait asszociált típusa maga is generikus legyen élettartam vagy típus paraméterek szerint, ami komplex absztrakciók (pl. iterátor-szerű streaming API-k) írásához nyit új utat.
A let-else szintaxis pedig egy régi bosszúságot old meg: amikor egy mintaillesztés sikertelensége esetén korai kilépésre van szükség, nem kell már csúnya match blokkot írni:
fn parse_config(input: &str) -> Option<u32> {
let Ok(value) = input.trim().parse::<u32>() else {
println!("nem sikerült a parse-olás: {input}");
return None;
};
Some(value * 2)
}
Ez sokkal olvashatóbb, mint a régi if let ... else { return None; } mintázat, különösen ha több feltételt kell egymás után ellenőrizni.
Hogyan érdemes frissíteni a projektjeinket
A frissítés szerencsére a szokásos módon zajlik, és mivel az 1.67 (és az azóta megjelent 1.68) teljesen visszafelé kompatibilis a korábbi stabil kiadásokkal, nincs szükség kódmódosításra:
rustup update stable
cargo build --release
Mielőtt élesben is bevetnéd az új verziót, néhány dolgot mégis érdemes átgondolni:
- Futtasd le a teszteket – főleg, ha a kódbázisod erősen épít
mpsccsatornákra konkurens tesztek szinkronizálásához. Az új implementáció viselkedése specifikáció szerint azonos, de érdemes ellenőrizni. - Ellenőrizd a warningokat – a
#[must_use]async fn-es kiterjesztése miatt lehet, hogy most először jelennek meg figyelmeztetések olyan helyeken, ahol korábban csendben elnyelted egyFutureeredményét. Ezek jó eséllyel valódi hibákra hívják fel a figyelmet. - CI konfiguráció – ha
rustcverziót pinnelsz a CI-ban, frissítsd azt is, hogy kihasználhasd a csatorna-teljesítmény javulást a build- és tesztfutásokban is, amennyiben azok konkurens komponenseket tesztelnek.
Ha a kódbázisodban valaki korábban tudatosan hagyott figyelmen kívül egy async fn visszatérési értékét (pl. egy „fire and forget” háttérfeladat elindításánál), az 1.67 után figyelmeztetést kaphatsz erre. Ha ez szándékos, ne feledd explicit módon jelezni: let _ = fetch_value(); vagy hasonló mintával némítsd el a warningot, hogy tiszta maradjon a build log.
Összefoglalás
Az 1.67-es Rust kiadás jó példája annak, hogy egy release nem mindig a legvillámfényesebb nyelvi feature-ökről szól – a std::sync::mpsc csendes, de alapos újraírása minden olyan projektet érint, amely szálak közötti kommunikációra épít, és a háttérben komoly teljesítmény- és megbízhatóság-javulást hoz. A #[must_use] async fn-ekre való kiterjesztése pedig kis, de annál hasznosabb védőháló azoknak, akik gyakran dolgoznak async kóddal. Ha még nem tetted, most van itt az idő, hogy frissítsd a toolchain-edet, és élvezd a gyorsabb, stabilabb csatornákat – kód módosítása nélkül.