Minden hatodik hétben új stabil Rust érkezik, és ez a ritmus most sem tört meg: 2023. március 9-én kiadták a Rust 1.68-at. Elsőre talán csendesebb release-nek tűnik, mint amikor GATs-ot vagy let-else-t kaptunk, de a mostani két fő újdonság — a Cargo sparse registry protokollja és a pin! makró — pont azok közé tartozik, amik nem a nyelv felszínén látszanak, hanem a napi munkafolyamatot teszik simábbá.

Cargo sparse registry: végre gyorsabb cargo build

Aki már használt nagyobb Rust projektet, tudja, milyen érzés, amikor egy tiszta gépen lefuttatod a cargo build-et, és a Cargo először is leszedi a teljes crates.io indexet egy git repóból. Ez a git-alapú index több százezer crate metaadatát tartalmazza, és a klónozása bizony érezhető időbe (és sávszélességbe) kerül — főleg CI környezetben, ahol minden egyes futtatás gyakorlatilag nulláról indul.

A Rust 1.68-ban a sparse registry protokoll stabil elérhetőségűvé vált. Ahelyett, hogy a teljes indexet letöltenéd, a Cargo mostantól HTTP-n keresztül, igény szerint ("sparse", azaz szórt/ritka módon) kéri le csak azoknak a crate-eknek a metaadatait, amikre ténylegesen szükség van a függőségi gráfodban.

Tipp

A sparse protokoll egyelőre nem alapértelmezett a crates.io-ra — ehhez explicit be kell kapcsolnod. A .cargo/config.toml fájlodban add hozzá a következőt:

[registries.crates-io]
protocol = "sparse"

Vagy környezeti változóval, projekt módosítása nélkül: CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse.

A gyakorlatban ez főleg CI pipeline-okon és nagyobb monorepo-kon érezhető igazán — a build előtti "index frissítés" lépés drasztikusan lerövidülhet. Saját gépi projekteknél, ahol az index már cache-elve van, kisebb a különbség, de az első cargo build élménye is sokkal barátságosabb lesz azoknak, akik most csatlakoznak egy csapathoz.

Megjegyzés

A csapat jelezte, hogy hosszabb távon a sparse protokoll lesz az alapértelmezett a crates.io-hoz — ez egyelőre terv, nem jelen idejű változás, szóval addig is érdemes manuálisan bekapcsolni, ha CI-t optimalizálnál.

A pin! makró: kevesebb boilerplate az async kódban

Ha valaha próbáltál stack-en pinnelt Future-t létrehozni kézzel unsafe nélkül, tudod, hogy ez korábban meglehetősen kényelmetlen volt. A Pin típus és a hozzá tartozó szabályok fontosak az async Rust működéséhez, de a helyes, biztonságos pinnelés megírása gyakran extra crate-eket (pl. pin-project vagy pin-utils) igényelt.

A Rust 1.68 stabilizálja a std könyvtárban a std::pin::pin! makrót, amivel egyszerűen, biztonságosan hozhatsz létre egy stack-re pinnelt értéket, external crate nélkül:

use std::pin::pin;
use std::future::Future;

async fn valami_lassu() -> u32 {
    42
}

fn main() {
    // Csak illusztráció: valódi futtatáshoz executor kell,
    // pl. tokio vagy egy egyszerű block_on.
    let fut = valami_lassu();
    let pinned = pin!(fut);

    // A `pinned` mostantól Pin<&mut impl Future<Output = u32>>
    // és tovább adható polling célra.
    let _: Pin<&mut _> = pinned;
}

A pin! makró lényegében azt csinálja, amit korábban a pin_utils::pin_mut! vagy hasonló segédmakrók: egy lokális változót hoz létre, és azt helyben pinneli, elkerülve a heap allokációt (amit egy Box::pin igényelne). Ez különösen hasznos, ha saját executor-t írsz, vagy alacsony szintű async kódot fejlesztesz, ahol minden allokáció számít.

use std::pin::pin;
use std::future::Future;
use std::task::{Context, Poll};

fn poll_once<F: Future>(fut: F) -> Poll<F::Output> {
    let mut fut = pin!(fut);
    // Egyszerűsített, csak demonstrációs célú waker nélkül nem futtatható éles kódban,
    // de jól mutatja, hogyan válik elérhetővé a Pin<&mut F> egy async fn-ből.
    let _ = &mut fut;
    Poll::Pending
}
Figyelem

A fenti második példa didaktikus, valódi pollinghoz megfelelő Waker-re és Context-re van szükség — éles kódban használj executor-t (pl. tokio::main), ne kézzel pollingolj Future-öket, hacsak nem pont ez a feladatod (executor írás).

Mit jelent ez a mindennapi munkában?

Ha nem írsz saját async runtime-ot vagy executor-t, a pin! makró közvetlen hatása valószínűleg kicsi lesz a napi kódodban — a legtöbb alkalmazásfejlesztő ritkán nyúl közvetlenül Pin-hez, hiszen a tokio és hasonló runtime-ok ezt már elrejtik előlünk. Ahol viszont igazán érezhető a különbség, az a build- és CI-idő: a sparse registry protokoll bekapcsolása gyakorlatilag ingyen kapott sebességnövekedés, különösen ha GitHub Actions-ön vagy más felhős CI-n futtatod a teszteket, és minden egyes futásnál újra le kellene húzni a teljes crates.io indexet.

Érdemes tehát:

  • ha CI-t üzemeltetsz, próbáld ki a protocol = "sparse" beállítást, és mérd meg a build időt előtte-utána;
  • ha alacsony szintű async kódot írsz (pl. saját Future implementációt), nézz rá a pin! makróra — lehet, hogy kiválthatsz vele egy külső függőséget.

Hogyan frissíts?

A frissítés a megszokott módon zajlik, a rustup segítségével:

rustup update stable

Ez letölti és beállítja az 1.68-as toolchaint, ha még nem lenne meg. Ellenőrizni a verziót így tudod:

rustc --version
# rustc 1.68.0 (2c8cc3432 2023-03-06)
Jó tudni

A cargo.lock fájlok és a meglévő projektjeid teljesen kompatibilisek maradnak — a Rust csapat szigorúan tartja magát a visszafelé kompatibilitási ígéretéhez, szóval a frissítés gyakorlatilag mindig kockázatmentes.

Összefoglalás

A Rust 1.68 nem egy hatalmas, nyelvi paradigmaváltó release, hanem inkább a mindennapi fejlesztői élményt csiszoló verzió. A sparse registry protokoll komoly build- és CI-gyorsulást hozhat, ha bekapcsolod, a pin! makró pedig egyre kevésbé teszi szükségessé külső crate-ek bevonását az alacsony szintű async munkához. Ha eddig még nem tetted, most van itt az ideje egy rustup update stable-nek — a frissítés, mint mindig, most is fájdalommentes.