Az előző leckében megismerted a skalár és összetett típusokat — most itt az ideje, hogy ezekkel valamit is csináljunk. Egy program ritkán fut le egyenes vonalban felülről lefelé: döntéseket kell hoznia ("ha ez igaz, csináld ezt, ha nem, csináld azt"), és gyakran ugyanazt a műveletet többször meg kell ismételnie. Ebben a leckében a Rust három alapvető vezérlési eszközét nézzük végig: a feltételes elágazást (if/else), a mintaillesztést (match), és a ciklusokat (loop, while, for).

Feltételes elágazás: if és else

A Rust if-je nagyon hasonlít más nyelvekéhez, egy fontos különbséggel: a feltétel köré nem kell zárójel, a kapcsos zárójelek pedig kötelezőek — nincs egysoros "zárójel nélküli" if.

fn main() {
    let temperature = 21;

    if temperature > 25 {
        println!("Meleg van, {temperature} fok.");
    } else if temperature > 15 {
        println!("Kellemes idő, {temperature} fok.");
    } else {
        println!("Hűvös van, {temperature} fok.");
    }
}

Fontos, hogy a feltételnek mindig bool típusúnak kell lennie — a Rust nem alakít át automatikusan más típust (pl. 0-t vagy ""-t) igazságértékké, mint néhány dinamikus nyelv.

fn main() {
    let count = 0;

    if count {
        println!("ez nem fog fordulni");
    }
}
error[E0308]: mismatched types
  --> src/main.rs:4:8
   |
4  |     if count {
   |        ^^^^^ expected `bool`, found integer

Az if-nek van egy különleges tulajdonsága is: kifejezésként is használható, azaz visszaadhat egy értéket, amit el is tárolhatsz egy let-tel. Ilyenkor minden ágnak ugyanolyan típust kell visszaadnia, és nincs pontosvessző az utolsó kifejezés után:

fn main() {
    let temperature = 8;

    let advice = if temperature < 10 {
        "Vegyél fel kabátot!"
    } else {
        "Póló is elég lesz."
    };

    println!("{advice}");
}
Tipp

Ez az if-kifejezés minta gyakran szebb, mint egy let mut deklaráció, amit utána feltételesen módosítasz — érdemes megszokni.

Mintaillesztés: a match alapjai

A match a Rust egyik legkedveltebb szerkezete: egy értéket több mintával hasonlít össze, és lefuttatja az elsőt, amelyik illik. Egy nagy előnye, hogy kimerítőnek (exhaustive) kell lennie — a fordító megköveteli, hogy minden lehetséges esetet lefedj, különben nem fordul a kód. Ez sok futásidejű hibától megóv.

fn main() {
    let day_number = 3;

    let day_name = match day_number {
        1 => "hétfő",
        2 => "kedd",
        3 => "szerda",
        4 => "csütörtök",
        5 => "péntek",
        6 | 7 => "hétvége",
        _ => "ismeretlen nap",
    };

    println!("A {day_number}. nap: {day_name}");
}

A | jellel egy ágban több mintát is megadhatsz ("vagy"), az alávonás (_) pedig a "minden más eset" jokerkarakter — ha kihagynád, a fordító hibát dobna, mert nem minden i32 értékre lenne definiálva a viselkedés.

A match ágakban tartományokat (range-eket) is használhatsz, ami különösen kényelmes számokra vagy karakterekre:

fn main() {
    let score = 78;

    let grade = match score {
        90..=100 => 'A',
        80..=89 => 'B',
        70..=79 => 'C',
        60..=69 => 'D',
        _ => 'F',
    };

    println!("A {score} pontszámhoz tartozó jegy: {grade}");
}

A 90..=100 egy zárt tartomány (inclusive range): mindkét végpontját beleérti. Ha csak 90..100-at írnál, akkor a 100 már nem tartozna bele.

Megjegyzés

A match sokkal többre képes, mint amit itt látsz — tuple-öket, több változót egyszerre és bonyolultabb mintákat is tud illeszteni. Most csak az alapokra koncentrálunk, a mélyebb lehetőségek egy későbbi leckében jönnek elő.

Ismétlés: loop, while és for

A Rustban három ciklusfajta van, mindegyiknek megvan a maga jó helye.

A loop a legegyszerűbb: a végtelenségig fut, amíg egy break-kel ki nem lépsz belőle. Előnye, hogy a break után adhatsz vissza egy értéket is:

fn main() {
    let mut counter = 0;

    let result = loop {
        counter += 1;

        if counter == 5 {
            break counter * 2;
        }
    };

    println!("Az eredmény: {result}");
}

A while ciklus addig fut, amíg egy feltétel igaz:

fn main() {
    let mut fuel = 5;

    while fuel > 0 {
        println!("Üzemanyag: {fuel}");
        fuel -= 1;
    }

    println!("Kifogyott az üzemanyag!");
}

A for ciklus pedig egy sorozaton lépked végig — ez a leggyakrabban használt forma, mert nem kell manuálisan kezelned a számlálót:

fn main() {
    let numbers = [10, 20, 30, 40];

    for number in numbers {
        println!("Szám: {number}");
    }
}
Megjegyzés

A for ciklus a háttérben azt a mechanizmust használja, amivel a Rust bármin végig tud iterálni — erről (az Iterator trait-ről) egy későbbi leckében tanulsz majd részletesen. Most elég annyit tudnod, hogy tömbökön és range-eken simán működik.

Range-ek: 0..10 és 0..=10

A for ciklust nagyon gyakran range-ekkel kombináljuk, ha egyszerűen csak számolni akarunk. A 0..10 egy félig zárt tartomány: 0-tól 9-ig tart (a 10 nincs bele), míg a 0..=10 zárt tartomány, 0-tól 10-ig.

fn main() {
    println!("Félig zárt tartomány:");
    for i in 0..5 {
        println!("  i = {i}");
    }

    println!("Zárt tartomány:");
    for i in 0..=5 {
        println!("  i = {i}");
    }
}

Ha csökkenő sorrendben szeretnél végigmenni számokon, azt a for ciklus önmagában (range-dzsel) nem tudja megtenni fordított irányban — erre majd egy későbbi leckében kapsz eleganciás eszközt. Most egyszerűen while ciklussal is megoldható:

fn main() {
    let mut countdown = 5;

    while countdown > 0 {
        println!("{countdown}...");
        countdown -= 1;
    }

    println!("Indulás!");
}

Gyakorlati példa: mindent egybe

Nézzünk egy programot, amely az if, a match és a for ciklust is kombinálja: 1-től 20-ig kiírja a számokat, és minden szám mellé odaírja, hogy hányassal osztható.

fn main() {
    for number in 1..=20 {
        let divisible_by_3 = number % 3 == 0;
        let divisible_by_5 = number % 5 == 0;

        let label = match (divisible_by_3, divisible_by_5) {
            (true, true) => "Fizzbuzz",
            (true, false) => "Fizz",
            (false, true) => "Buzz",
            (false, false) => "",
        };

        if label.is_empty() {
            println!("{number}");
        } else {
            println!("{number} -> {label}");
        }
    }
}

Itt egy tuple-t ((divisible_by_3, divisible_by_5)) illesztünk a match-csel — ez jól mutatja, hogy a match több változó kombinációját is egyszerűen kezelheti, nem csak egyetlen értéket.

Próbáld ki!

Írj egy programot, amely egy let age = 17; sorral indul, és if/else if/else szerkezettel kiírja, hogy a személy "gyerek" (0-12), "tinédzser" (13-17) vagy "felnőtt" (18 fölött). Ezután írd meg ugyanezt match-csel is, ..= tartományokkal — melyik verziót találod olvashatóbbnak?

Önellenőrzés

1. Miért kell a match kifejezésnek kimerítőnek lennie, és mi történik, ha kihagysz egy esetet?

Megoldás

A Rust fordító megköveteli, hogy a match minden lehetséges bemeneti értékre definiáljon egy viselkedést, mert így garantálható, hogy futásidőben soha nem marad kezeletlen eset. Ha kihagysz egy esetet (és nincs _ joker ág), fordítási hibát kapsz — ez sok potenciális hibát kiszűr még azelőtt, hogy a program elindulna.

2. Mi a különbség a 0..5 és a 0..=5 range között?

Megoldás

A 0..5 félig zárt (exclusive) tartomány: 0, 1, 2, 3, 4 — a felső határ (5) nincs bele. A 0..=5 zárt (inclusive) tartomány: 0, 1, 2, 3, 4, 5 — a felső határ is beletartozik.

3. Mikor válasszunk loop-ot while helyett?

Megoldás

A loop-ot érdemes választani, ha nincs egyszerű, előre megfogalmazható feltétel a ciklus elején (pl. a kilépési feltétel csak a ciklus közepén derül ki), vagy ha a ciklusból egy értéket is vissza akarsz adni a break segítségével. A while-t akkor használd, ha van egy tiszta, a ciklus elején kiértékelhető logikai feltételed.

Összefoglalás

  • Az if/else a Rustban is feltételes elágazásra szolgál, de kifejezésként is használható, így egyenesen egy let-be írhatod az eredményét.
  • A match kimerítő mintaillesztést biztosít: minden esetet le kell fednie, ami sok futásidejű hibát megelőz — használhatsz benne konkrét értékeket, |-lel több minta, _ jokert és ..= tartományokat is.
  • A loop, while és for mind ismétlésre szolgál, de más a jó felhasználási területük: loop végtelen/feltétel nélküli ismétlésre és értékvisszaadásra, while feltétel-vezérelt ismétlésre, for pedig sorozatokon (tömbök, range-ek) való egyszerű végigjárásra.
  • A 0..10 és 0..=10 range-ek gyakori társai a for ciklusnak, és a match ágakban is jól használhatók.
  • A következő leckében egy ellenőrző kvíz vár rád, amiben összefoglalod, mennyire ülnek már az eddig tanult alapok — az if/else, match és ciklusok mind visszatérnek benne.

Ha mélyebben el akarsz merülni a témában, nézd meg a Rust hivatalos könyvének vezérlési szerkezetekről szóló fejezetét: Control Flow – The Rust Programming Language, valamint a match referencia-dokumentációját: match Expressions.