28 KiB
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.
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:
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():
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
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
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
// Ú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) -> f64obvod(&self) -> f64je_stvorec(&self) -> boolzvacsit(&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?
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š?
Pes::new("Rex", 5) // ???
rex.stekaj() // ???
Kapitola 2: Metódy vracajúce referencie
Metódy často vracajú referencie na interné dáta:
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
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í
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áš:
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.
// 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.
// 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.
// 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) -> f64vloz(&mut self, suma: f64)vyber(&mut self, suma: f64) -> Result<(), ()>
Kapitola 4: impl Trait for — implementácia traitu
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():
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:
// 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
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×π×rObdlznik { 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:
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?
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.
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.
// 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ť
MockIOManagerktorý vracia vopred určené písmená
mut v parametri
// 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:
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áš:
trait Validator {
fn je_platny(&self, vstup: &str) -> bool;
}
Implementuj pre:
DlzkaValidator { min: usize, max: usize }— dĺžka v rozsahuEmailValidator— 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:
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.
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:
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:
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
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:
// 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áš:
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).
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
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
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 |
#[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:
enum VysledokPokusu { Uhadnute, Neuhadnute, Neplatne }
Výstup: "Uhádnuté", "Neuhádnuté", "Neplatné".
7b. Implementuj Display pre:
struct Zamestnanec { meno: String, pozicia: String, plat: u32 }
Výstup: "Anna (Programátor) - 3000€"
7c. Implementuj Display pre enum aj štruktúru:
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.
// 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:
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:
struct Nastavenia {
jazyk: String, // "sk"
tema: String, // "svetla"
velkost_fontu: u8, // 14
zvuk: bool, // true
}
8b. Implementuj Default pre enum:
enum Obtiaznost { Lahka, Stredna, Tazka }
Default: Stredna.
Kapitola 9: Viacero traitov na jednom type
Jedna štruktúra môže implementovať koľko traitov chce:
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
#[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:
#[derive(...)]— automatické traityimpl Display for Kniha— ručný traitimpl 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:
// 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.
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
VysledokPokusus derive a Display - Trait
IOManagers metódouziskaj_pismeno - Struct
ConsoleIOManagera jeho impl IOManager
10b. Bez pozerania, napíš struct Hra a tieto metódy:
Hra::new(slovo: &str) -> Hratipni_pismeno(&mut self, pismeno: char) -> VysledokPokusudaj_uhadnute_pismena(&self) -> impl Iterator<Item = &char>daj_slovo_skryto(&self) -> Stringje_koniec_hry(&self) -> bool
10c. Bez pozerania, napíš struct SpravcaHry a:
nacitaj_zo_suboru(cesta: &PathBuf) -> Option<SpravcaHry>uloz_do_suboru(&self, cesta: &PathBuf) -> boolvytvor_novu_hru(&self, kategoria: &str) -> Option<Hra>pridaj_kategoriu(&mut self, kategoria: &str) -> Result<(), ()>