Files
JR-priprava-na-skusku/priprava/rust_priprava12.md
2026-03-05 12:09:44 +01:00

1134 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# impl, trait, impl Trait for — krok za krokom
Každá kapitola: prečítaj, pozri príklady, urob úlohy. Až potom choď ďalej.
---
## Kapitola 1: impl blok — metódy a asociované funkcie
`impl` blok pridáva funkcie ku štruktúre alebo enumu.
```rust
struct Pes {
meno: String,
vek: u8,
}
impl Pes {
// Toto sú funkcie, ktoré patria k Pes
}
```
Dva typy vecí v impl bloku:
### Asociovaná funkcia — nemá self
Volá sa cez `Typ::nazov()`. Najčastejšie `new`:
```rust
impl Pes {
fn new(meno: &str, vek: u8) -> Pes {
Pes {
meno: meno.to_string(),
vek,
}
}
}
// Volanie:
let rex = Pes::new("Rex", 5);
```
Nemá `self` → nie je viazaná na konkrétnu inštanciu → voláš cez `Pes::new()`.
### Metóda — má self
Volá sa cez `instancia.nazov()`:
```rust
impl Pes {
fn stekaj(&self) {
println!("{} štekol!", self.meno);
}
}
// Volanie:
let rex = Pes::new("Rex", 5);
rex.stekaj();
```
`&self` → viazaná na inštanciu → voláš cez `rex.stekaj()`.
### 3 varianty self
```rust
impl Pes {
// &self — len čítanie, inštancia žije ďalej
fn meno(&self) -> &str {
&self.meno
}
// &mut self — mení dáta, inštancia žije ďalej
fn premenuj(&mut self, nove_meno: &str) {
self.meno = nove_meno.to_string();
}
// self — skonzumuje inštanciu, po volaní neexistuje
fn rozluc_sa(self) -> String {
format!("Zbohom, {}!", self.meno)
// po tomto rex neexistuje
}
}
```
| Signatúra | Čo môžeš | Inštancia po volaní |
|-----------|----------|---------------------|
| `&self` | čítať | žije ďalej |
| `&mut self` | čítať + meniť | žije ďalej |
| `self` | čítať + meniť + konzumovať | **neexistuje** |
### Kedy let vs let mut
```rust
let rex = Pes::new("Rex", 5);
rex.stekaj(); // OK — &self, len čítame
rex.meno(); // OK — &self
// rex.premenuj("Fido"); // CHYBA! premenuj je &mut self, ale rex nie je mut
let mut rex = Pes::new("Rex", 5);
rex.premenuj("Fido"); // OK — &mut self a rex je mut
```
### Viacero impl blokov
```rust
// Úplne legálne — Rust ich spojí
impl Pes {
fn new(meno: &str, vek: u8) -> Pes { /* ... */ }
}
impl Pes {
fn stekaj(&self) { /* ... */ }
}
```
### Úlohy 1
**1a.** Napíš štruktúru `Obdlznik { sirka: f64, vyska: f64 }` a impl blok s:
- `new(sirka, vyska) -> Obdlznik` (asociovaná funkcia)
- `obsah(&self) -> f64`
- `obvod(&self) -> f64`
- `je_stvorec(&self) -> bool`
- `zvacsit(&mut self, faktor: f64)` — vynásob obe strany
**1b.** Napíš štruktúru `Pocitadlo { hodnota: i32 }` s:
- `new() -> Pocitadlo` (začni na 0)
- `zvys(&mut self)`
- `zniz(&mut self)`
- `resetuj(&mut self)`
- `hodnota(&self) -> i32`
Zavolaj každú metódu a over výsledok.
**1c.** Čo je zle?
```rust
struct Bod { x: f64, y: f64 }
impl Bod {
fn posun(&mut self, dx: f64, dy: f64) {
self.x += dx;
self.y += dy;
}
}
fn main() {
let b = Bod { x: 0.0, y: 0.0 };
b.posun(1.0, 2.0);
}
```
**1d.** Čo je rozdiel? Kedy ktoré použiješ?
```rust
Pes::new("Rex", 5) // ???
rex.stekaj() // ???
```
---
## Kapitola 2: Metódy vracajúce referencie
Metódy často vracajú referencie na interné dáta:
```rust
struct Osoba {
meno: String,
zaujmy: Vec<String>,
}
impl Osoba {
// Vráť referenciu na String — volajúci nemôže meniť
fn meno(&self) -> &str {
&self.meno
}
// Vráť referenciu na Vec — volajúci nemôže meniť
fn zaujmy(&self) -> &Vec<String> {
&self.zaujmy
}
// Vráť kópiu — volajúci dostane vlastnú hodnotu
fn meno_owned(&self) -> String {
self.meno.clone()
}
}
```
### Vracanie Option s referenciou
```rust
struct Skupina {
clenovia: Vec<String>,
}
impl Skupina {
// Nájdi člena — vráť referenciu zabalenú v Option
fn najdi(&self, meno: &str) -> Option<&String> {
self.clenovia.iter().find(|c| c.as_str() == meno)
}
// Nájdi index a odstráň — &mut self lebo meníme Vec
fn odstran(&mut self, meno: &str) -> Result<String, ()> {
let pos = self.clenovia.iter()
.position(|c| c == meno)
.ok_or(())?;
Ok(self.clenovia.remove(pos))
}
}
```
### Vracanie Vec referencií
```rust
impl Skupina {
// Filtrovanie — vráť Vec referencií
fn zacinajuce_na(&self, pismeno: char) -> Vec<&String> {
self.clenovia.iter()
.filter(|c| c.starts_with(pismeno))
.collect()
}
}
```
### Úlohy 2
**2a.** Máš:
```rust
struct Kniznica { knihy: Vec<Kniha> }
struct Kniha { nazov: String, autor: String, rok: u16 }
```
Napíš:
- `najdi_podla_nazvu(&self, nazov: &str) -> Option<&Kniha>`
- `knihy_autora(&self, autor: &str) -> Vec<&Kniha>`
- `odstran(&mut self, nazov: &str) -> Result<Kniha, ()>`
- `najnovsia(&self) -> Option<&Kniha>`
**2b.** Prečo `najdi_podla_nazvu` vracia `Option<&Kniha>` a nie `Option<Kniha>`?
**2c.** Prečo `odstran` vracia `Result<Kniha, ()>` a nie `Result<&Kniha, ()>`?
---
## Kapitola 3: Trait — čo to je
Trait je **sada metód**, ktoré typ musí implementovať. Je to ako rozhranie (interface) v iných jazykoch.
```rust
// Definícia traitu — hovorí ČO treba vedieť
trait Zviera {
fn zvuk(&self) -> &str;
fn meno(&self) -> &str;
}
```
Trait hovorí: "ktokoľvek chce byť Zviera, musí vedieť `zvuk()` a `meno()`."
### Prečo traity?
Bez traitov: každý typ má vlastné metódy s rôznymi názvami.
```rust
// BEZ traitu — chaos
impl Pes { fn stekni(&self) -> &str { "Haf!" } }
impl Macka { fn maukni(&self) -> &str { "Miau!" } }
impl Krava { fn buckni(&self) -> &str { "Múú!" } }
// Každý má iný názov metódy — nemôžeš ich volať jednotne
```
S traitom: všetky typy majú rovnakú metódu.
```rust
// S traitom — poriadok
trait Zviera {
fn zvuk(&self) -> &str;
}
// Teraz každý implementuje rovnakú metódu zvuk()
// a ty ich môžeš volať jednotne
```
### Úlohy 3
**3a.** Čo je trait vlastnými slovami?
**3b.** Navrhni trait `Tvar` s metódami `obsah(&self) -> f64` a `obvod(&self) -> f64`. Len definuj trait, nič neimplementuj.
**3c.** Navrhni trait `Ucet` pre bankový účet s metódami:
- `zostatok(&self) -> f64`
- `vloz(&mut self, suma: f64)`
- `vyber(&mut self, suma: f64) -> Result<(), ()>`
---
## Kapitola 4: impl Trait for — implementácia traitu
```rust
trait Zviera {
fn zvuk(&self) -> &str;
fn meno(&self) -> &str;
}
struct Pes {
meno: String,
}
struct Macka {
meno: String,
}
// Implementácia traitu Zviera PRE typ Pes
impl Zviera for Pes {
fn zvuk(&self) -> &str {
"Haf!"
}
fn meno(&self) -> &str {
&self.meno
}
}
// Implementácia traitu Zviera PRE typ Macka
impl Zviera for Macka {
fn zvuk(&self) -> &str {
"Miau!"
}
fn meno(&self) -> &str {
&self.meno
}
}
```
Teraz obe zvieratá majú metódy `zvuk()` a `meno()`:
```rust
let rex = Pes { meno: "Rex".into() };
let micka = Macka { meno: "Micka".into() };
println!("{} robí {}", rex.meno(), rex.zvuk()); // Rex robí Haf!
println!("{} robí {}", micka.meno(), micka.zvuk()); // Micka robí Miau!
```
### Trait + vlastný impl blok
Štruktúra môže mať **aj** trait metódy **aj** vlastné metódy:
```rust
// Trait metódy
impl Zviera for Pes {
fn zvuk(&self) -> &str { "Haf!" }
fn meno(&self) -> &str { &self.meno }
}
// Vlastné metódy (nie z traitu)
impl Pes {
fn new(meno: &str) -> Pes {
Pes { meno: meno.to_string() }
}
fn aport(&self) {
println!("{} ide po loptičku!", self.meno);
}
}
let rex = Pes::new("Rex");
rex.zvuk(); // z traitu
rex.aport(); // vlastná metóda
```
### Musíš implementovať VŠETKY metódy
```rust
trait Zviera {
fn zvuk(&self) -> &str;
fn meno(&self) -> &str;
}
// CHYBA — chýba meno()
impl Zviera for Pes {
fn zvuk(&self) -> &str { "Haf!" }
}
// error: not all trait items implemented, missing: `meno`
```
### Úlohy 4
**4a.** Máš trait z 3b (`Tvar` s `obsah` a `obvod`). Implementuj ho pre:
- `Kruh { polomer: f64 }` — obsah = π×r², obvod = 2×π×r
- `Obdlznik { a: f64, b: f64 }` — obsah = a×b, obvod = 2×(a+b)
- `Trojuholnik { a: f64, b: f64, c: f64, vyska_a: f64 }` — obsah = a×vyska_a/2, obvod = a+b+c
**4b.** Implementuj trait `Ucet` z 3c pre:
```rust
struct SporiaciUcet { zostatok: f64 }
struct BeznyUcet { zostatok: f64, limit: f64 }
```
BeznyUcet má limit prečerpania — vyber() môže ísť do mínusu až po limit.
**4c.** Čo je zle?
```rust
trait Pozdrav {
fn pozdrav(&self) -> String;
}
struct Robot;
impl Pozdrav for Robot { }
```
---
## Kapitola 5: Trait s parametrom — impl Trait v argumentoch
Trait môžeš použiť ako typ parametra. Funkcia potom príjme **akýkoľvek typ**, ktorý implementuje daný trait.
```rust
trait Zviera {
fn zvuk(&self) -> &str;
fn meno(&self) -> &str;
}
// Funkcia príjme čokoľvek čo je Zviera
fn predstav(zviera: &impl Zviera) {
println!("{} robí {}", zviera.meno(), zviera.zvuk());
}
let rex = Pes::new("Rex");
let micka = Macka { meno: "Micka".into() };
predstav(&rex); // OK — Pes implementuje Zviera
predstav(&micka); // OK — Macka implementuje Zviera
```
### Na skúške — IOManager pattern
Na skúške je typický vzor: metóda berie `impl Trait` parameter pre I/O operácie. Toto umožňuje testovanie — na skúšku sa dá ConsoleIOManager, na testy MockIOManager.
```rust
// Trait definuje AKO sa získava vstup
trait IOManager {
fn ziskaj_pismeno(&mut self) -> char;
}
// Reálna implementácia — číta z konzoly
struct ConsoleIOManager;
impl IOManager for ConsoleIOManager {
fn ziskaj_pismeno(&mut self) -> char {
let mut vstup = String::new();
std::io::stdin().read_line(&mut vstup).unwrap();
vstup.trim().chars().next().unwrap_or(' ')
}
}
// Hra používa trait, nie konkrétny typ
struct Hra {
slovo: String,
// ...
}
impl Hra {
// Berie impl IOManager — funguje s akoukoľvek implementáciou
fn hraj(&mut self, mut io: impl IOManager) {
loop {
let pismeno = io.ziskaj_pismeno();
// ... spracuj písmeno ...
if self.je_koniec() { break; }
}
}
fn je_koniec(&self) -> bool { todo!() }
}
// Volanie:
fn main() {
let mut hra = Hra { slovo: "rust".into() };
hra.hraj(ConsoleIOManager);
}
```
Prečo `impl IOManager` a nie priamo `ConsoleIOManager`?
- Ak napíšeš `io: ConsoleIOManager`, metóda funguje len s konzolou
- Ak napíšeš `io: impl IOManager`, funguje s čímkoľvek čo implementuje IOManager
- Testy môžu použiť `MockIOManager` ktorý vracia vopred určené písmená
### mut v parametri
```rust
// Ak trait metóda má &mut self, parameter musí byť mut
fn hraj(&mut self, mut io: impl IOManager) {
// ^^^ mut lebo ziskaj_pismeno je &mut self
let pismeno = io.ziskaj_pismeno();
}
// Ak trait metóda má &self, netreba mut
trait Zobrazovac {
fn zobraz(&self, text: &str);
}
fn vypis(zobrazovac: &impl Zobrazovac) {
zobrazovac.zobraz("ahoj");
}
```
### Úlohy 5
**5a.** Máš trait:
```rust
trait Logger {
fn log(&self, sprava: &str);
}
```
Implementuj ho pre:
- `KonzolaLogger` — vypíše do println!
- `SuborLogger { cesta: String }` — len println! s prefixom "[FILE]"
- `TichyLogger` — nič nerobí
Napíš funkciu `spracuj(data: &str, logger: &impl Logger)` ktorá zaloguje "Spracovávam: {data}".
**5b.** Máš:
```rust
trait Validator {
fn je_platny(&self, vstup: &str) -> bool;
}
```
Implementuj pre:
- `DlzkaValidator { min: usize, max: usize }` — dĺžka v rozsahu
- `EmailValidator` — obsahuje '@' a '.'
Napíš funkciu `validuj(vstup: &str, validator: &impl Validator) -> bool`.
**5c.** Prečo `hraj` berie `mut io: impl IOManager` a nie `io: &impl IOManager`?
---
## Kapitola 6: Returning impl Trait — vracanie impl
### impl Iterator — najčastejšie na skúške
Keď metóda vracia iterátor, typ iterátora je komplikovaný. `impl Iterator` ho skryje:
```rust
struct Skupina {
mena: Vec<String>,
vyluceni: HashSet<String>,
}
impl Skupina {
// Vráť iterátor cez referencie na mená
fn aktivni(&self) -> impl Iterator<Item = &String> {
self.mena.iter().filter(|m| !self.vyluceni.contains(*m))
}
}
```
**Čo to znamená:** Funkcia vráti "niečo čo je Iterator a dáva &String". Volajúci nevie presný typ, ale vie iterovať.
### Konkrétne na skúške — iterátor na člen štruktúry
Najčastejší vzor: vráť iterátor priamo na pole štruktúry.
```rust
use std::collections::HashSet;
struct Hra {
hladane_slovo: String,
uhadnute_pismena: HashSet<char>,
skusane_pismena: HashSet<char>,
aktualny_pocet_zivotov: u8,
}
impl Hra {
// Vráť iterátor na uhadnute_pismena
fn daj_uhadnute_pismena(&self) -> impl Iterator<Item = &char> {
self.uhadnute_pismena.iter()
}
// Vráť iterátor na skusane_pismena
fn daj_skusane_pismena(&self) -> impl Iterator<Item = &char> {
self.skusane_pismena.iter()
}
}
```
Volajúci potom môže:
```rust
let hra = Hra::new("rust");
// Iterovať
for pismeno in hra.daj_uhadnute_pismena() {
println!("{}", pismeno);
}
// Collect do Vec
let pismena: Vec<&char> = hra.daj_uhadnute_pismena().collect();
// Count
let pocet = hra.daj_skusane_pismena().count();
```
### Pravidlo: Item typ = čo .iter() dáva
| Kolekcia | `.iter()` dáva | Item typ |
|----------|---------------|----------|
| `Vec<String>` | `&String` | `Item = &String` |
| `HashSet<char>` | `&char` | `Item = &char` |
| `HashMap<K, V>` | `(&K, &V)` | `Item = (&K, &V)` |
| `Vec<Kniha>` | `&Kniha` | `Item = &Kniha` |
Takže:
```rust
fn daj_knihy(&self) -> impl Iterator<Item = &Kniha> {
self.knihy.iter()
}
fn daj_kategorie(&self) -> impl Iterator<Item = (&String, &Vec<String>)> {
self.slovnik.iter()
}
```
### impl Iterator s filtrom
```rust
impl Kniznica {
// Filtrovanie — stále len impl Iterator
fn knihy_autora(&self, autor: &str) -> impl Iterator<Item = &Kniha> + '_ {
self.knihy.iter().filter(move |k| k.autor == autor)
}
}
```
**`+ '_`** — lifecycle annotation. Treba ho ak closure v `.filter()` zachytáva parameter. Ak ti kompilér kričí o lifetimoch pri impl Iterator s filtrom, pridaj `+ '_`.
**`move`** — closure zachytí `autor` hodnotou. Treba ak filter používa parameter funkcie.
Pre skúšku: ak ti to robí problémy, jednoducho vráť `Vec<&Kniha>` namiesto `impl Iterator`. Je to menej elegantné, ale funguje:
```rust
// Jednoduchšia alternatíva — vždy funguje, žiadne lifetime problémy
fn knihy_autora(&self, autor: &str) -> Vec<&Kniha> {
self.knihy.iter().filter(|k| k.autor == autor).collect()
}
```
### Úlohy 6
**6a.** Máš:
```rust
struct Trieda {
studenti: Vec<String>,
znamky: HashMap<String, Vec<u8>>,
}
```
Napíš:
- `daj_studentov(&self) -> impl Iterator<Item = &String>`
- `daj_znamky(&self, student: &str) -> Option<&Vec<u8>>`
**6b.** Máš `struct Kosik { polozky: HashSet<String> }`. Napíš:
- `daj_polozky(&self) -> impl Iterator<Item = &String>`
**6c.** Kedy vrátit `impl Iterator<Item = &T>` a kedy `Vec<&T>`?
---
## Kapitola 7: Display trait — impl fmt::Display
Na skúške skoro vždy treba implementovať Display, aby si mohol robiť `println!("{}", instancia)`.
```rust
use std::fmt;
struct Kniha {
nazov: String,
autor: String,
rok: u16,
}
impl fmt::Display for Kniha {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} — {} ({})", self.nazov, self.autor, self.rok)
}
}
// Teraz funguje:
let kniha = Kniha { nazov: "Duna".into(), autor: "Herbert".into(), rok: 1965 };
println!("{}", kniha); // "Duna — Herbert (1965)"
```
### Display pre enum
```rust
use std::fmt;
enum Stav { Nova, Pouzivana, Poskodena, Vyradena }
impl fmt::Display for Stav {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Stav::Nova => write!(f, "Nová"),
Stav::Pouzivana => write!(f, "Používaná"),
Stav::Poskodena => write!(f, "Poškodená"),
Stav::Vyradena => write!(f, "Vyradená"),
}
}
}
// Teraz funguje:
println!("{}", Stav::Nova); // "Nová"
```
### Display pre štruktúru s enumom
```rust
impl fmt::Display for Kniha {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} — {} ({}) [{}]", self.nazov, self.autor, self.rok, self.stav)
// ^^^^^^^^
// stav.fmt() sa zavolá automaticky
// lebo Stav implementuje Display
}
}
println!("{}", kniha); // "Duna — Herbert (1965) [Nová]"
```
### Display vs Debug
| Trait | Macro | Derive | Účel |
|-------|-------|--------|------|
| `Display` | `{}` | NIE — musíš ručne | Pre používateľa |
| `Debug` | `{:?}` | ÁNO — `#[derive(Debug)]` | Pre programátora |
```rust
#[derive(Debug)] // Debug cez derive
struct Bod { x: f64, y: f64 }
impl fmt::Display for Bod { // Display ručne
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
let b = Bod { x: 1.0, y: 2.0 };
println!("{}", b); // (1, 2) — Display
println!("{:?}", b); // Bod { x: 1.0, y: 2.0 } — Debug
```
### Úlohy 7
**7a.** Implementuj Display pre:
```rust
enum VysledokPokusu { Uhadnute, Neuhadnute, Neplatne }
```
Výstup: "Uhádnuté", "Neuhádnuté", "Neplatné".
**7b.** Implementuj Display pre:
```rust
struct Zamestnanec { meno: String, pozicia: String, plat: u32 }
```
Výstup: `"Anna (Programátor) - 3000€"`
**7c.** Implementuj Display pre enum aj štruktúru:
```rust
enum Priorita { Nizka, Stredna, Vysoka }
struct Uloha { nazov: String, priorita: Priorita, hotova: bool }
```
Uloha výstup: `"[✓] Upratovanie (Vysoká)"` alebo `"[ ] Upratovanie (Vysoká)"` podľa hotova.
---
## Kapitola 8: Default trait — ručne
`#[derive(Default)]` funguje keď chceš štandardné default hodnoty (0, "", vec![], None). Keď chceš vlastné, implementuj ručne.
```rust
// derive Default — automatické
#[derive(Default)]
struct Config {
port: u16, // 0
host: String, // ""
debug: bool, // false
}
// Ručný Default — vlastné hodnoty
struct Config2 {
port: u16,
host: String,
debug: bool,
}
impl Default for Config2 {
fn default() -> Self {
Config2 {
port: 8080, // vlastná hodnota
host: "localhost".to_string(),
debug: false,
}
}
}
let c = Config2::default();
// port = 8080, host = "localhost", debug = false
```
### Default pre enum
Enum nemá automatický default — musíš povedať ktorý variant:
```rust
enum Stav { Nova, Pouzivana, Poskodena, Vyradena }
impl Default for Stav {
fn default() -> Self {
Stav::Nova // Nova je default
}
}
let s = Stav::default(); // Stav::Nova
```
### Úlohy 8
**8a.** Implementuj Default pre:
```rust
struct Nastavenia {
jazyk: String, // "sk"
tema: String, // "svetla"
velkost_fontu: u8, // 14
zvuk: bool, // true
}
```
**8b.** Implementuj Default pre enum:
```rust
enum Obtiaznost { Lahka, Stredna, Tazka }
```
Default: Stredna.
---
## Kapitola 9: Viacero traitov na jednom type
Jedna štruktúra môže implementovať koľko traitov chce:
```rust
use std::fmt;
use serde::{Serialize, Deserialize};
// Enum s veľa traitmi
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
enum Stav { Nova, Pouzivana, Poskodena, Vyradena }
// Ručné traity
impl fmt::Display for Stav {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Stav::Nova => write!(f, "Nová"),
Stav::Pouzivana => write!(f, "Používaná"),
Stav::Poskodena => write!(f, "Poškodená"),
Stav::Vyradena => write!(f, "Vyradená"),
}
}
}
impl Default for Stav {
fn default() -> Self { Stav::Nova }
}
// Teraz Stav má: Debug, Clone, PartialEq, Serialize, Deserialize, Display, Default
```
### Štruktúra s derive + ručné traity + vlastný impl
```rust
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Kniha {
nazov: String,
autor: String,
stav: Stav,
}
// Display — ručne
impl fmt::Display for Kniha {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} — {} [{}]", self.nazov, self.autor, self.stav)
}
}
// Vlastné metódy
impl Kniha {
fn new(nazov: &str, autor: &str) -> Kniha {
Kniha {
nazov: nazov.to_string(),
autor: autor.to_string(),
stav: Stav::default(),
}
}
}
```
Tri rôzne impl bloky pre jeden typ — úplne OK:
1. `#[derive(...)]` — automatické traity
2. `impl Display for Kniha` — ručný trait
3. `impl Kniha` — vlastné metódy
### Úlohy 9
**9a.** Vytvor kompletný enum `VysledokPokusu` s hodnotami `Uhadnute, Neuhadnute, Neplatne`. Pridaj:
- derive: Debug, Clone, PartialEq
- Display: vlastné slovenské texty
Napíš 3 testy, kde porovnávaš varianty cez `==` a vypisuješ cez `{}`.
**9b.** Vytvor kompletný typ pre skúšku:
```rust
// Enum Stav s derive + Display + Default
// Struct Kniha s derive + Display + new()
// Struct Kniznica s derive + Default + nacitaj/uloz + metódy
```
Napíš len signatúry a derive/impl hlavičky — telo metód nechaj ako `todo!()`.
---
## Kapitola 10: Kompletný vzor — Obesenec zo skúšky
Toto je celý vzor, ako by vyzerala skúška. Prejdi si ho riadok po riadku.
```rust
use std::collections::HashSet;
use std::fmt;
use serde::{Serialize, Deserialize};
use std::path::PathBuf;
use std::collections::HashMap;
use rand::seq::SliceRandom;
// ============================================================
// ENUM — derive + Display
// ============================================================
#[derive(Debug, Clone, PartialEq)]
pub enum VysledokPokusu {
Uhadnute,
Neuhadnute,
Neplatne,
}
impl fmt::Display for VysledokPokusu {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
VysledokPokusu::Uhadnute => write!(f, "Uhádnuté"),
VysledokPokusu::Neuhadnute => write!(f, "Neuhádnuté"),
VysledokPokusu::Neplatne => write!(f, "Neplatné"),
}
}
}
// ============================================================
// TRAIT — definícia rozhrania
// ============================================================
pub trait IOManager {
fn ziskaj_pismeno(&mut self) -> char;
}
// ============================================================
// ŠTRUKTURA Hra — logika hry
// ============================================================
pub struct Hra {
hladane_slovo: String,
uhadnute_pismena: HashSet<char>,
skusane_pismena: HashSet<char>,
aktualny_pocet_zivotov: u8,
}
// Vlastné metódy (asociované funkcie + metódy)
impl Hra {
// Asociovaná funkcia — Hra::new()
pub fn new(slovo: &str) -> Hra {
Hra {
hladane_slovo: slovo.to_lowercase(),
uhadnute_pismena: HashSet::new(),
skusane_pismena: HashSet::new(),
aktualny_pocet_zivotov: 5,
}
}
// &mut self — mení stav hry
pub fn tipni_pismeno(&mut self, pismeno: char) -> VysledokPokusu {
let p = pismeno.to_lowercase().next().unwrap();
// Už bolo skúšané → Neplatné
if !self.skusane_pismena.insert(p) {
return VysledokPokusu::Neplatne;
}
// Je v slove → Uhádnuté
if self.hladane_slovo.contains(p) {
self.uhadnute_pismena.insert(p);
VysledokPokusu::Uhadnute
} else {
self.aktualny_pocet_zivotov -= 1;
VysledokPokusu::Neuhadnute
}
}
// &self — len čítanie, vráti iterátor
pub fn daj_uhadnute_pismena(&self) -> impl Iterator<Item = &char> {
self.uhadnute_pismena.iter()
}
pub fn daj_skusane_pismena(&self) -> impl Iterator<Item = &char> {
self.skusane_pismena.iter()
}
// &self — vracia nový String
pub fn daj_slovo_skryto(&self) -> String {
self.hladane_slovo.chars().map(|c| {
if self.uhadnute_pismena.contains(&c) { c } else { '_' }
}).collect()
}
pub fn daj_dlzku_slova(&self) -> usize {
self.hladane_slovo.chars().count()
}
pub fn je_koniec_hry(&self) -> bool {
self.aktualny_pocet_zivotov == 0 || self.je_vyhra()
}
pub fn je_vyhra(&self) -> bool {
self.hladane_slovo.chars()
.all(|c| self.uhadnute_pismena.contains(&c))
}
// &mut self + impl IOManager parameter
pub fn hraj(&mut self, mut io: impl IOManager) {
while !self.je_koniec_hry() {
// Vypíš stav
println!("Životov: {}", self.aktualny_pocet_zivotov);
println!("Slovo: {}", self.daj_slovo_skryto());
let skusane: Vec<&char> = self.daj_skusane_pismena().collect();
println!("Skúšané: {:?}", skusane);
// Získaj písmeno cez IOManager trait
let pismeno = io.ziskaj_pismeno();
// Tipni a vypíš výsledok
let vysledok = self.tipni_pismeno(pismeno);
println!("{}", vysledok);
}
// Koniec
if self.je_vyhra() {
println!("Výhra! Slovo bolo: {}", self.hladane_slovo);
} else {
println!("Prehra! Slovo bolo: {}", self.hladane_slovo);
}
}
}
// ============================================================
// ŠTRUKTURA SpravcaHry — serde + HashMap
// ============================================================
#[derive(Serialize, Deserialize, Default)]
pub struct SpravcaHry {
slovnik: HashMap<String, Vec<String>>,
}
impl SpravcaHry {
// Statická — SpravcaHry::nacitaj_zo_suboru()
pub fn nacitaj_zo_suboru(cesta: &PathBuf) -> Option<SpravcaHry> {
let raw = std::fs::read_to_string(cesta).ok()?;
serde_json::from_str(&raw).ok()
}
// &self metóda
pub fn uloz_do_suboru(&self, cesta: &PathBuf) -> bool {
let Ok(json) = serde_json::to_string_pretty(&self) else {
return false;
};
std::fs::write(cesta, json).is_ok()
}
// &self — vracia Option<Hra>
pub fn vytvor_novu_hru(&self, kategoria: &str) -> Option<Hra> {
let slova = self.slovnik.get(kategoria)?;
if slova.is_empty() { return None; }
let mut rng = rand::thread_rng();
let slovo = slova.choose(&mut rng)?;
Some(Hra::new(slovo))
}
// &mut self
pub fn pridaj_kategoriu(&mut self, kategoria: &str) -> Result<(), ()> {
if self.slovnik.contains_key(kategoria) {
return Err(());
}
self.slovnik.insert(kategoria.to_string(), Vec::new());
Ok(())
}
// &mut self
pub fn pridaj_slovo(&mut self, kategoria: &str, slovo: &str) -> Result<(), ()> {
let slova = self.slovnik.get_mut(kategoria).ok_or(())?;
slova.push(slovo.to_string());
Ok(())
}
}
// ============================================================
// IMPL TRAIT FOR — ConsoleIOManager implementuje IOManager
// ============================================================
pub struct ConsoleIOManager;
impl IOManager for ConsoleIOManager {
fn ziskaj_pismeno(&mut self) -> char {
let mut vstup = String::new();
std::io::stdin().read_line(&mut vstup).unwrap();
vstup.trim().chars().next().unwrap_or(' ')
}
}
```
### Úlohy 10
**10a.** Bez pozerania na kód vyššie, napíš:
- Enum `VysledokPokusu` s derive a Display
- Trait `IOManager` s metódou `ziskaj_pismeno`
- Struct `ConsoleIOManager` a jeho impl IOManager
**10b.** Bez pozerania, napíš struct `Hra` a tieto metódy:
- `Hra::new(slovo: &str) -> Hra`
- `tipni_pismeno(&mut self, pismeno: char) -> VysledokPokusu`
- `daj_uhadnute_pismena(&self) -> impl Iterator<Item = &char>`
- `daj_slovo_skryto(&self) -> String`
- `je_koniec_hry(&self) -> bool`
**10c.** Bez pozerania, napíš struct `SpravcaHry` a:
- `nacitaj_zo_suboru(cesta: &PathBuf) -> Option<SpravcaHry>`
- `uloz_do_suboru(&self, cesta: &PathBuf) -> bool`
- `vytvor_novu_hru(&self, kategoria: &str) -> Option<Hra>`
- `pridaj_kategoriu(&mut self, kategoria: &str) -> Result<(), ()>`