Ez a kvíz az egész útvonal anyagát összeköti, kiemelten arra épülve, amit a „Hibakezelés-projekt” leckében gyakoroltál. Ne aggódj, ha nem megy elsőre minden: minden kérdés után ott a magyarázat, és a végén megtalálod, melyik leckéhez érdemes visszalapozni.

Tipp

Próbáld meg fejben (vagy papíron) megválaszolni a kérdést, mielőtt kinyitod a megoldást – így sokkal többet ér a gyakorlás.

Trait-ek és dinamikus dispatch

1. kérdés. Melyik állítás igaz a statikus és a dinamikus dispatch közötti különbségről?

A) A dyn Trait fordításidőben dönt, melyik metódust hívja. B) A generikus (<T: Trait>) függvényeket a compiler monomorfizálja, ezért minden konkrét típushoz külön gépi kód keletkezik. C) A Box<dyn Trait> mindig gyorsabb, mint a generikus verzió. D) A statikus dispatch futásidőben egy vtable-t használ.

Megoldás

A helyes válasz B. A generikákra a compiler minden konkrét típusra lefordítja a kódot (monomorfizáció), így nincs futásidejű keresés. A dyn Trait ezzel szemben vtable-ön keresztül hív metódust, futásidőben – ez lehet kicsit lassabb, de rugalmasabb (pl. heterogén kollekciókhoz).

2. kérdés. Miért nem lehet a következő trait-et dyn Trait-ként használni?

trait Cloner {
    fn duplicate<T>(&self, value: T) -> T;
}
Megoldás

A duplicate metódus generikus paramétert (<T>) tartalmaz, emiatt a trait nem object-safe: a vtable-nek fix méretű, konkrét metóduslistára lenne szüksége, de egy generikus metódusból elméletileg végtelen sok verzió készülhetne. Az ilyen trait-eket csak statikus dispatch-csel (impl Cloner vagy <T: Cloner>) lehet használni.

Iterátorok

3. kérdés. Mit ír ki ez a kód?

fn main() {
    let numbers = vec![1, 2, 3, 4, 5, 6];
    let result: Vec<i32> = numbers
        .iter()
        .filter(|&&n| n % 2 == 0)
        .map(|&n| n * 10)
        .collect();
    println!("{:?}", result);
}
Megoldás

[20, 40, 60]. A filter csak a páros számokat engedi tovább (2, 4, 6), a map mindegyiket megszorozza tízzel. Az adapterek lusták, tényleges munka csak a collect hívásakor történik.

4. kérdés. Adott egy saját Iterator implementáció. Mit ír ki a main?

struct Countdown(u32);

impl Iterator for Countdown {
    type Item = u32;

    fn next(&mut self) -> Option<u32> {
        if self.0 == 0 {
            None
        } else {
            self.0 -= 1;
            Some(self.0 + 1)
        }
    }
}

fn main() {
    let sum: u32 = Countdown(3).sum();
    println!("{sum}");
}
Megoldás

6. A Countdown(3) iterátor sorban 3, 2, 1-et ad vissza, mielőtt None-t adna, a sum() adapter ezeket összeadja: 3 + 2 + 1 = 6. Mivel implementáltuk az Iterator trait-et, minden beépített adapter (itt a sum) automatikusan elérhető.

Lifetime-ok

5. kérdés. Kell-e explicit lifetime annotáció ehhez a függvényhez, és ha nem, miért?

fn first_word(text: &str) -> &str {
    text.split_whitespace().next().unwrap_or("")
}
Megoldás

Nem kell explicit annotáció, mert az elision-szabályok alapján a compiler egyértelműen tudja: egyetlen bemenő referencia van, így a visszaadott referencia lifetime-ja ugyanaz, mint a paraméteré. A compiler ezt automatikusan fn first_word<'a>(text: &'a str) -> &'a str-ként kezeli.

6. kérdés. Fordul-e ez a kódrészlet? (nem önálló program, csak a lényeg kiemelve)

// kódrészlet
fn longest_of_two<'a>(a: &'a str, first: &str) -> &'a str {
    if a.len() > first.len() { a } else { first }
}
Megoldás

Nem fordul. A visszatérési érték 'a lifetime-mal van jelölve, de a függvény néha a first paramétert adja vissza, amelynek nincs 'a-hoz kötött lifetime-ja – a compiler nem tudja garantálni, hogy first legalább addig érvényes, mint amit ígérünk. Mindkét paramétert 'a-val kellene jelölni.

Hibakezelés

7. kérdés. Mit ír ki ez a kód?

fn parse_and_double(input: &str) -> Result<i32, std::num::ParseIntError> {
    let number: i32 = input.parse()?;
    Ok(number * 2)
}

fn main() {
    match parse_and_double("21") {
        Ok(value) => println!("Siker: {value}"),
        Err(error) => println!("Hiba: {error}"),
    }
}
Megoldás

Siker: 42. A ? operátor a parse() sikeres eredményét kicsomagolja, majd a függvény a duplázott értéket Ok-ba csomagolva adja vissza. Hiba esetén a ? azonnal visszatérne az Err-rel, itt viszont nincs hiba.

8. kérdés. Melyik állítás igaz a thiserror crate-tel definiált hibaenumokra?

A) A #[error(...)] attribútum automatikusan implementálja a Display trait-et. B) A thiserror helyettesíti a Result típust. C) A #[from] attribútum nélkül soha nem lehet ?-t használni más hibatípusokra. D) A thiserror csak panic!-kal kombinálva működik.

Megoldás

A helyes válasz A. A #[error("...")] attribútum a Display implementációt generálja le, míg a #[from] attribútum a From trait implementációját adja hozzá az adott variánshoz, amivel a ? operátor automatikusan konvertálhat mögöttes hibatípusokból a saját enumunkba.

Vegyes kérdés

9. kérdés. Melyik generikus függvénydeklaráció helyes, ha azt szeretnénk, hogy a T típus legyen összehasonlítható és formázható is?

A) fn show<T>(value: T) where T: PartialOrd { ... } B) fn show<T: PartialOrd + std::fmt::Debug>(value: T) { ... } C) fn show<T>(value: dyn T) { ... } D) fn show(value: PartialOrd + std::fmt::Debug) { ... }

Megoldás

A helyes válasz B. A trait bound-okat +-szal lehet összefűzni egy generikus paraméteren; ugyanezt where klózban is le lehetne írni (where T: PartialOrd + std::fmt::Debug). A dyn T szintaktikailag hibás, hiszen T itt egy generikus paraméter, nem konkrét trait objektum.

Hogyan tovább?

Ha a trait-ekkel és a dyn/generikák közti különbséggel küzdöttél, nézd át újra a „Trait-ek alapjai” és a „Generikák” leckéket. Ha az iterátoros kérdéseknél akadtál el, érdemes visszalapozni az „Iterator kézi implementálása” anyaghoz. Lifetime-hibák esetén az elision-szabályokat és a struct-lifetime leckét ismételd át, hibakezelésnél pedig a ? operátor és a thiserror leckéket.

Jó tudni

Ha ezt a kvízt sikeresen megoldottad, gratulálok – végigjártad a teljes „Trait-ek és generikák” útvonalat! Innentől a kódod nem csak fordul, hanem valóban idiomatikus, „rustos” Rust kód: trait-ekkel absztrahálsz, generikákkal újrahasznosítasz, iterátorokkal deklaratívan dolgozol, és a hibáidat típusosan kezeled. Ez szilárd alap ahhoz, hogy a következő útvonalakon aszinkron programozással, webszerverekkel és adatbázisokkal is magabiztosan dolgozz.