A Rust hatheti release-ciklusa idén is pontosan araszol tovább: 2023. január 26-án jelent meg a Rust 1.67, alig három nappal azután, hogy a legtöbbünk visszaszokott a munkarendbe az ünnepek után. Ez a kiadás nem hoz látványos, marketingszempontból "sexy" nyelvi feature-t, de két olyan apró finomítást tartalmaz, ami a mindennapi kódolás minőségét emeli — pont az a fajta változás, amiért a Rust csapatot szeretjük.
Ha kimaradtál az elmúlt hónapok híreiből: a Rust már stabilan tudja a GAT-okat (Generic Associated Types) és a let-else szintaxist is, szóval ha még nem próbáltad ki őket, ideje bepótolni.
#[must_use] async fn-eken
Eddig, ha egy sima függvény #[must_use] attribútummal volt megjelölve, a fordító szólt, ha az eredményét figyelmen kívül hagytad. Ez klasszikusan a Result-nál fájt a legjobban — senki nem akarja véletlenül elnyelni egy hibát.
A gond az volt, hogy async fn esetén ez az attribútum korábban nem viselkedett konzisztensen: az async fn valójában egy impl Future-t ad vissza, és ha ezt a Future-t soha nem .await-eled, akkor egyszerűen semmi nem történik — a kód lefordul, csendben, és a hívó azt hiheti, hogy lezajlott valami, miközben a jövő sosem futott le.
A Rust 1.67-től kezdve az async fn-ekre tett #[must_use] megbízhatóan figyelmeztet, ha a visszaadott Future-t nem használod:
#[must_use]
async fn fetch_config() -> Result<String, std::io::Error> {
// képzeljük el, hogy ez tényleg beolvas valamit
Ok("config-adat".to_string())
}
async fn hasznal() {
fetch_config(); // figyelmeztetést kapunk: a Future eredménye nincs felhasználva
let _ = fetch_config().await; // ez rendben van
}
Ha lefuttatod ezt cargo build-del, a fordító szépen rászól, hogy a fetch_config() hívás eredménye (a Future) figyelmen kívül lett hagyva. Ez különösen hasznos olyan async kódnál, ahol könnyű elfelejteni egy .await-et — típusosan copy-paste hiba, vagy amikor egy async lezáró (closure) belsejéből hiányzik egy sor.
Ha saját async API-t írsz és fontos, hogy a hívó ténylegesen lefuttassa a visszaadott Future-t (pl. egy tranzakciót commitoló async metódusnál), tedd rá bátran a #[must_use]-t — mostantól garantáltan működik.
Újraírt mpsc csatorna implementáció
A másik nagy belső változás a std::sync::mpsc modult érinti. A többtermelős, egyfogyasztós (multi-producer, single-consumer) csatorna a Rust egyik legrégebbi, legtöbbet használt konkurencia-eszköze, ugyanakkor az eredeti implementációja évek óta cipelt magával néhány kellemetlen, jól dokumentált hibát — például a recv_timeout időnként pontatlanul viselkedett, és voltak edge case-ek, amik race condition-höz vezethettek szélsőséges terhelés alatt.
A Rust 1.67-ben ezt a belső implementációt teljesen újraírták, egy modernebb, jobban tesztelt algoritmusra alapozva. A jó hír, hogy ez teljesen transzparens változás: a publikus API (channel(), sync_channel(), Sender, Receiver) egy karakternyit sem változott, viszont a viselkedés korrektebb és a teljesítmény is jobb lett, különösen sok termelő esetén.
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
fn main() {
let (tx, rx) = mpsc::channel();
for i in 0..4 {
let tx = tx.clone();
thread::spawn(move || {
tx.send(format!("üzenet a(z) {i}. szálról")).unwrap();
});
}
drop(tx); // az eredeti sender-t is el kell engedni
while let Ok(msg) = rx.recv_timeout(Duration::from_millis(500)) {
println!("{msg}");
}
}
Ez a kód 1.67 alatt fordul és fut, és most már a recv_timeout is megbízhatóbban viselkedik pontos időzítés mellett, mint a korábbi verziókban.
Ha korábban valamilyen "trükközéssel" kerülted meg a régi mpsc implementáció ismert hibáit (pl. saját timeout-logikát írtál helyette), most érdemes visszatérni a standard megoldásra — kevesebb kód, kevesebb hiba.
Mit jelent ez a gyakorlatban?
Ha async kóddal dolgozol — akár saját executor-ral, akár egy külső runtime-mal, mint a tokio —, az #[must_use] async fn-eken azonnal segít elkapni azokat a hibákat, ahol egy Future-t elindítasz, de elfelejted .await-elni. Ez a fajta hiba különösen alattomos, mert a kód lefordul és futni is látszik, csak épp a várt mellékhatás nem történik meg.
use std::sync::mpsc;
#[must_use]
async fn kuld_uzenetet(tx: mpsc::Sender<i32>, ertek: i32) {
tx.send(ertek).unwrap();
}
A mpsc újraírás pedig azoknak jó hír, akik konkurenciakritikus, sok szálas rendszereket építenek: a standard könyvtár csatornája most már megbízhatóbb alap, nem kell azon agyalni, hogy inkább egy külső crate-et (pl. crossbeam-channel) használj-e helyette "biztonság kedvéért".
Összességében ez a release pont azt a Rust-filozófiát testesíti meg, hogy a nyelv és a standard könyvtár folyamatosan finomodik anélkül, hogy a meglévő kódodat eltörné — a channel() API ugyanaz maradt, csak a motorháztető alatt lett jobb.
Hogyan frissíts?
A frissítés a megszokott módon, egyetlen paranccsal elintézhető:
rustup update stable
Ezután érdemes ellenőrizni, hogy tényleg az 1.67-es verziót futtatod-e:
rustc --version
# rustc 1.67.0 (fc594f156 2023-01-24)
Ha CI-ban rögzített toolchain verziót használsz (pl. rust-toolchain.toml fájlban), ne felejtsd el frissíteni azt is, különben a fejlesztői gépeden más viselkedést kapsz, mint a build szerveren.
A jelenlegi edition továbbra is a 2021-es — az 1.67 nem hoz edition-váltást, szóval a Cargo.toml-odban nincs teendőd ezen a fronton.
Összefoglalás
A Rust 1.67 nem forradalmi kiadás, de két olyan javítást hoz, amik közvetlenül a mindennapi kód minőségét emelik: az async fn-ek #[must_use] figyelmeztetése segít elkerülni a néma, el nem indított Future-öket, az újraírt mpsc csatorna pedig szilárdabb alapot ad a standard könyvtári konkurenciakezeléshez. Ha eddig halogattad a frissítést, most van itt az ideje egy gyors rustup update-nek — a régi kódod garantáltan tovább fog fordulni, csak jobban fog viselkedni.