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.
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.
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.