1134 lines
28 KiB
Markdown
1134 lines
28 KiB
Markdown
# 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();
|
||
```
|
||
|
||
Má `&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<(), ()>`
|