# I/O v Ruste — krok za krokom Každá kapitola: prečítaj, pozri príklady, urob úlohy. Až potom choď ďalej. --- ## Kapitola 1: println! a print! — výstup ```rust // println! — vypíše + nový riadok println!("Ahoj svet"); // Ahoj svet\n // print! — vypíše BEZ nového riadku print!("Zadaj meno: "); // Zadaj meno: _ (kurzor zostane na tom istom riadku) ``` ### Formátovanie ```rust let meno = "Anna"; let vek = 25; let priemer = 1.847; // Základné println!("{} má {} rokov", meno, vek); // Anna má 25 rokov // Zaokrúhlenie na 2 desatinné println!("Priemer: {:.2}", priemer); // Priemer: 1.85 // Zarovnanie println!("{:<10} | {:>5}", "Meno", "Vek"); println!("{:<10} | {:>5}", "Anna", 25); println!("{:<10} | {:>5}", "Boris", 30); // Meno | Vek // Anna | 25 // Boris | 30 // Padding znakom println!("{:-^20}", "MENU"); // --------MENU-------- // Debug výpis let vektor = vec![1, 2, 3]; println!("{:?}", vektor); // [1, 2, 3] println!("{:#?}", vektor); // pekný formát na viac riadkov ``` ### eprintln! — chybový výstup ```rust // eprintln! ide na stderr (nie stdout) eprintln!("Chyba: súbor neexistuje"); // V praxi: normálne správy cez println!, chyby cez eprintln! ``` ### Úlohy 1 **1a.** Vypíš tabuľku 3 produktov s názvom (zarovnaný vľavo na 15), cenou (zarovnaná vpravo na 8, 2 desatinné) a počtom (zarovnaný vpravo na 5): ``` Chlieb | 1.20 | 50 Mlieko | 0.89 | 120 Maslo | 2.49 | 30 ``` **1b.** Máš Vec<(String, f64)> — mená a priemery. Vypíš ich zoradené podľa priemeru, formátované na 1 desatinné miesto. --- ## Kapitola 2: stdin — čítanie vstupu ```rust use std::io; fn main() { // Základné čítanie riadku let mut vstup = String::new(); io::stdin().read_line(&mut vstup).unwrap(); // vstup teraz obsahuje text + '\n' na konci! let vstup = vstup.trim(); // odstráň \n a medzery println!("Napísal si: {}", vstup); } ``` ### Krok po kroku ```rust // 1. Vytvor prázdny String (buffer) let mut vstup = String::new(); // 2. Prečítaj riadok do bufferu // read_line PRIDÁVA do stringu (neprepisuje!) // Vracia Result (počet prečítaných bajtov) io::stdin().read_line(&mut vstup).unwrap(); // 3. VŽDY trimni — read_line pridáva '\n' let vstup = vstup.trim(); ``` ### Čítanie čísla ```rust fn nacitaj_cislo() -> Option { let mut vstup = String::new(); io::stdin().read_line(&mut vstup).ok()?; vstup.trim().parse().ok() } // Použitie: match nacitaj_cislo() { Some(n) => println!("Číslo: {}", n), None => println!("To nie je číslo"), } ``` ### Čítanie jedného znaku ```rust fn nacitaj_znak() -> Option { let mut vstup = String::new(); std::io::stdin().read_line(&mut vstup).ok()?; vstup.trim().chars().next() } // Použitie: match nacitaj_znak() { Some(c) => println!("Znak: {}", c), None => println!("Prázdny vstup"), } ``` ### Pozor: read_line pridáva, neprepisuje! ```rust let mut vstup = String::new(); io::stdin().read_line(&mut vstup).unwrap(); // "ahoj\n" io::stdin().read_line(&mut vstup).unwrap(); // "ahoj\nsvet\n" — PRIDALO! // Ak chceš čítať viackrát, buď trimuj alebo vytvor nový String let mut vstup = String::new(); io::stdin().read_line(&mut vstup).unwrap(); let prvy = vstup.trim().to_string(); vstup.clear(); // VYMAŽ pred ďalším čítaním! io::stdin().read_line(&mut vstup).unwrap(); let druhy = vstup.trim().to_string(); ``` ### Úlohy 2 **2a.** Napíš program, ktorý prečíta meno a vek od používateľa a vypíše `"Ahoj Anna, máš 25 rokov"`. Vek parsuj na u32. **2b.** Napíš funkciu `nacitaj_riadok() -> Option` — prečíta riadok, trimne, vráti None ak je prázdny. **2c.** Napíš funkciu `nacitaj_float() -> Option` — prečíta riadok, trimne, parsne na f64. **2d.** Napíš program, ktorý opakovane číta čísla od používateľa kým nezadá "koniec". Na konci vypíše súčet. --- ## Kapitola 3: Game loop — opakované čítanie Na skúške je typický vzor: čítaj vstup v cykle, reaguj, skonči keď podmienka. ### Základný game loop ```rust use std::io; fn main() { loop { print!("Zadaj príkaz: "); let mut vstup = String::new(); io::stdin().read_line(&mut vstup).unwrap(); let vstup = vstup.trim(); match vstup { "koniec" => { println!("Zbohom!"); break; } "pomoc" => { println!("Príkazy: koniec, pomoc"); } _ => { println!("Neznámy príkaz: {}", vstup); } } } } ``` ### Game loop s parsovaním ```rust loop { print!("> "); let mut vstup = String::new(); io::stdin().read_line(&mut vstup).unwrap(); let casti: Vec<&str> = vstup.trim().split_whitespace().collect(); if casti.is_empty() { continue; } match casti[0] { "pridaj" => { if casti.len() < 2 { println!("Použitie: pridaj "); continue; } let meno = casti[1]; println!("Pridávam: {}", meno); } "vypis" => { println!("Výpis..."); } "koniec" => break, _ => println!("Neznámy príkaz"), } } ``` ### Úlohy 3 **3a.** Napíš jednoduchý kalkulátor. Používateľ zadáva `"5 + 3"`, `"10 - 4"`, `"koniec"`. Program počíta a vypisuje výsledok. **3b.** Napíš program, kde používateľ opakovane háda číslo 1-100. Program povie "viac", "menej" alebo "správne". Použi `rand::Rng`. --- ## Kapitola 4: Súborové I/O — čítanie ```rust use std::fs; // Prečítaj celý súbor naraz do String let obsah: Result = fs::read_to_string("data.txt"); match obsah { Ok(text) => println!("Obsah: {}", text), Err(e) => println!("Chyba: {}", e), } // Skrátene s .ok()? v funkcii fn nacitaj(cesta: &str) -> Option { fs::read_to_string(cesta).ok() } ``` ### Čítanie po riadkoch ```rust use std::fs; fn spracuj_subor(cesta: &str) { let obsah = fs::read_to_string(cesta).unwrap(); for (i, riadok) in obsah.lines().enumerate() { println!("{}: {}", i + 1, riadok); } } ``` ### BufReader — efektívne čítanie po riadkoch Pre veľké súbory je lepší BufReader — nečíta celý súbor do pamäte: ```rust use std::fs::File; use std::io::{BufRead, BufReader}; fn citaj_po_riadkoch(cesta: &str) -> Result<(), std::io::Error> { let subor = File::open(cesta)?; let citac = BufReader::new(subor); for riadok in citac.lines() { let riadok = riadok?; // každý riadok je Result println!("{}", riadok); } Ok(()) } ``` ### Čítanie bajtov ```rust // Celý súbor ako bajty let bajty: Vec = fs::read("obrazok.png").unwrap(); println!("Veľkosť: {} bajtov", bajty.len()); ``` ### Úlohy 4 **4a.** Napíš funkciu `pocet_riadkov(cesta: &str) -> Option` — prečíta súbor a vráti počet riadkov. **4b.** Napíš funkciu `najdi_v_subore(cesta: &str, hladany: &str) -> Vec` — vráti riadky, ktoré obsahujú hľadaný text. **4c.** Napíš funkciu `nacitaj_cisla(cesta: &str) -> Option>` — súbor obsahuje jedno číslo na riadok. Prečítaj, parsuj, vráť Vec. --- ## Kapitola 5: Súborové I/O — zápis ```rust use std::fs; // Zapíš celý String do súboru (prepíše ak existuje!) fs::write("data.txt", "Ahoj svet\nDruhý riadok\n").unwrap(); // Bezpečná verzia v funkcii fn uloz(cesta: &str, obsah: &str) -> bool { fs::write(cesta, obsah).is_ok() } ``` ### Pridávanie na koniec (append) ```rust use std::fs::OpenOptions; use std::io::Write; fn pripis(cesta: &str, text: &str) -> bool { let vysledok = OpenOptions::new() .create(true) // vytvor ak neexistuje .append(true) // pridávaj na koniec .open(cesta); match vysledok { Ok(mut subor) => { writeln!(subor, "{}", text).is_ok() } Err(_) => false, } } ``` ### BufWriter — efektívny zápis ```rust use std::fs::File; use std::io::{BufWriter, Write}; fn zapis_vela_riadkov(cesta: &str, riadky: &[String]) -> bool { let Ok(subor) = File::create(cesta) else { return false; }; let mut writer = BufWriter::new(subor); for riadok in riadky { if writeln!(writer, "{}", riadok).is_err() { return false; } } true } ``` ### Úlohy 5 **5a.** Napíš funkciu `uloz_riadky(cesta: &str, riadky: &[String]) -> bool` — ulož vektor riadkov do súboru, každý na nový riadok. **5b.** Napíš funkciu `pridaj_riadok(cesta: &str, riadok: &str) -> bool` — pridaj riadok na koniec súboru. **5c.** Napíš program, ktorý číta mená od používateľa a ukladá ich do `"mena.txt"`. Keď zadá "koniec", skončí. --- ## Kapitola 6: PathBuf a Path `PathBuf` je ako `String` pre cesty k súborom. `Path` je ako `&str`. ```rust use std::path::{PathBuf, Path}; // Vytváranie PathBuf let cesta = PathBuf::from("data/subory/kniznica.json"); let cesta: PathBuf = "data.json".into(); // Z &str let cesta = Path::new("data.json"); // Konverzia let pathbuf = PathBuf::from("data.json"); let path: &Path = &pathbuf; // PathBuf → &Path let path: &Path = pathbuf.as_path(); // to isté // Užitočné metódy let cesta = PathBuf::from("/home/user/data.json"); cesta.exists(); // existuje súbor? cesta.is_file(); // je to súbor? cesta.is_dir(); // je to priečinok? cesta.extension(); // Some("json") cesta.file_name(); // Some("data.json") cesta.parent(); // Some("/home/user") // Skladanie ciest let mut cesta = PathBuf::from("/home/user"); cesta.push("dokumenty"); cesta.push("subor.txt"); // /home/user/dokumenty/subor.txt ``` ### Na skúške — PathBuf v clape ```rust use clap::Parser; use std::path::PathBuf; #[derive(Parser)] struct Args { #[arg(short, long, default_value = "data.json")] subor: PathBuf, } fn main() { let args = Args::parse(); // args.subor je PathBuf — predávaš priamo do funkcií let kniznica = Kniznica::nacitaj_zo_suboru(&args.subor) .unwrap_or_default(); } ``` ### Funkcie berúce cestu ```rust // Berie &PathBuf — funguje s PathBuf fn nacitaj(cesta: &PathBuf) -> Option { std::fs::read_to_string(cesta).ok() } // Lepšie: berie &Path — funguje s PathBuf aj &str fn nacitaj(cesta: &Path) -> Option { std::fs::read_to_string(cesta).ok() } // fs::read_to_string berie impl AsRef — prijme takmer čokoľvek // Preto funguje: fs::read_to_string("data.json"); // &str fs::read_to_string(String::from("data.json")); // String fs::read_to_string(PathBuf::from("data.json")); // PathBuf fs::read_to_string(&pathbuf); // &PathBuf ``` ### Úlohy 6 **6a.** Vytvor PathBuf z `"/home/user/docs"`. Pridaj `"projekt"` a `"data.json"`. Vypíš celú cestu. **6b.** Máš PathBuf. Vypíš: existuje? Je súbor? Prípona? Názov súboru? Rodičovský priečinok? **6c.** Prečo na skúške metóda `nacitaj_zo_suboru` berie `&PathBuf` a nie `&str`? --- ## Kapitola 7: serde JSON I/O — kompletný vzor Toto je vzor na každej skúške. Kombinácia fs + serde_json. ```rust use std::fs; use std::path::PathBuf; use serde::{Serialize, Deserialize}; #[derive(Serialize, Deserialize, Default)] struct Kniznica { knihy: Vec, } #[derive(Serialize, Deserialize, Clone)] struct Kniha { nazov: String, autor: String, } impl Kniznica { // ČÍTANIE: súbor → String → štruktúra fn nacitaj_zo_suboru(cesta: &PathBuf) -> Option { // 1. fs::read_to_string — súbor → String let raw = fs::read_to_string(cesta).ok()?; // 2. serde_json::from_str — String → štruktúra serde_json::from_str(&raw).ok() } // ZÁPIS: štruktúra → String → súbor fn uloz_do_suboru(&self, cesta: &PathBuf) -> bool { // 1. serde_json::to_string_pretty — štruktúra → String let Ok(json) = serde_json::to_string_pretty(&self) else { return false; }; // 2. fs::write — String → súbor fs::write(cesta, json).is_ok() } } ``` Celý tok dát: ``` ČÍTANIE: súbor na disku → fs::read_to_string → String s JSON textom → serde_json::from_str → Rust štruktúra ZÁPIS: Rust štruktúra → serde_json::to_string_pretty → String s JSON textom → fs::write → súbor na disku ``` ### Použitie v main ```rust fn main() { let cesta = PathBuf::from("kniznica.json"); // Načítaj alebo vytvor prázdnu let mut kniznica = Kniznica::nacitaj_zo_suboru(&cesta) .unwrap_or_default(); // Urob niečo... kniznica.knihy.push(Kniha { nazov: "Duna".into(), autor: "Herbert".into(), }); // Ulož kniznica.uloz_do_suboru(&cesta); } ``` ### Čo sa deje keď súbor neexistuje ```rust // fs::read_to_string("neexistuje.json") // → Err(io::Error) // → .ok() → None // → ? → vráti None z funkcie // Preto: Kniznica::nacitaj_zo_suboru(&PathBuf::from("neexistuje.json")) // → None // A v main: let mut kniznica = Kniznica::nacitaj_zo_suboru(&cesta) .unwrap_or_default(); // None → unwrap_or_default() → Kniznica::default() → Kniznica { knihy: vec![] } ``` ### Úlohy 7 **7a.** Napíš `nacitaj_zo_suboru` a `uloz_do_suboru` pre: ```rust #[derive(Serialize, Deserialize, Default)] struct TodoList { ulohy: Vec, } #[derive(Serialize, Deserialize, Clone)] struct Todo { text: String, hotova: bool, } ``` **7b.** Napíš program, ktorý: 1. Načíta TodoList z "todo.json" 2. Pridá novú úlohu 3. Uloží späť 4. Znova načíta a vypíše počet úloh (overenie) **7c.** Čo sa stane ak `serde_json::to_string_pretty` zlyhá? Kedy sa to stane? (Hint: takmer nikdy pre bežné štruktúry.) --- ## Kapitola 8: IOManager trait — vzor zo skúšky Na skúške sa I/O abstrahuje cez trait. Toto umožňuje testovanie bez konzoly. ### Prečo nie priamo stdin? ```rust // PROBLÉM: toto nejde testovať automaticky fn hraj(hra: &mut Hra) { let mut vstup = String::new(); io::stdin().read_line(&mut vstup).unwrap(); // čaká na konzolu // ... } ``` ### Riešenie: IOManager trait ```rust // 1. Definuj trait — AKO získať vstup pub trait IOManager { fn ziskaj_pismeno(&mut self) -> char; } // 2. Reálna implementácia — čítaj z konzoly 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(' ') } } // 3. Metóda berie impl IOManager — funguje s čímkoľvek impl Hra { pub fn hraj(&mut self, mut io: impl IOManager) { while !self.je_koniec_hry() { println!("Slovo: {}", self.daj_slovo_skryto()); let pismeno = io.ziskaj_pismeno(); let vysledok = self.tipni_pismeno(pismeno); println!("{}", vysledok); } } } // 4. Volanie v main fn main() { let mut hra = Hra::new("rust"); hra.hraj(ConsoleIOManager); // predáme implementáciu } ``` ### ConsoleIOManager — prázdna štruktúra ```rust // Prázdna štruktúra — žiadne členy pub struct ConsoleIOManager; // Toto nie je typ s poľami. Je to "unit struct". // Vytváraš ju bez {} : let io = ConsoleIOManager; // OK // let io = ConsoleIOManager {}; // tiež OK ale zbytočné ``` ### Rozšírený IOManager Niekedy trait má viac metód: ```rust pub trait IOManager { fn ziskaj_pismeno(&mut self) -> char; fn zobraz_spravu(&self, sprava: &str); fn zobraz_stav(&self, zivoty: u8, slovo: &str, skusane: &[char]); } pub struct ConsoleIOManager; impl IOManager for ConsoleIOManager { fn ziskaj_pismeno(&mut self) -> char { print!("Zadaj písmeno: "); let mut vstup = String::new(); std::io::stdin().read_line(&mut vstup).unwrap(); vstup.trim().chars().next().unwrap_or(' ') } fn zobraz_spravu(&self, sprava: &str) { println!("{}", sprava); } fn zobraz_stav(&self, zivoty: u8, slovo: &str, skusane: &[char]) { println!("Životov: {}", zivoty); println!("Slovo: {}", slovo); println!("Skúšané: {:?}", skusane); } } ``` ### Úlohy 8 **8a.** Napíš trait `Vstup` s metódou `nacitaj_cislo(&mut self) -> Option`. Implementuj: - `KonzolaVstup` — číta z stdin - `TestovaciVstup { cisla: Vec, index: usize }` — vracia predpripravené čísla **8b.** Napíš funkciu `hadaj_cislo(tajne: i32, mut vstup: impl Vstup)` — hra na hádanie čísla. Používa trait namiesto priameho stdin. **8c.** Máš IOManager z príkladu vyššie. Napíš `TestIOManager`: ```rust struct TestIOManager { odpovede: Vec, index: usize, } ``` Implementuj IOManager — `ziskaj_pismeno` vráti ďalšie písmeno z `odpovede`. --- ## Kapitola 9: flush — print! bez nového riadku `print!` nevyprázdni buffer automaticky. Ak chceš aby sa text zobrazil okamžite pred čítaním vstupu: ```rust use std::io::{self, Write}; fn main() { print!("Zadaj meno: "); io::stdout().flush().unwrap(); // DÔLEŽITÉ! Vynúti výpis let mut vstup = String::new(); io::stdin().read_line(&mut vstup).unwrap(); println!("Ahoj, {}!", vstup.trim()); } ``` Bez `flush()` sa "Zadaj meno: " môže zobraziť až po tom, čo zadáš vstup. S `flush()` sa zobrazí hneď. ```rust // Kompletný vzor pre prompt fn prompt(text: &str) -> String { print!("{}", text); io::stdout().flush().unwrap(); let mut vstup = String::new(); io::stdin().read_line(&mut vstup).unwrap(); vstup.trim().to_string() } // Použitie: let meno = prompt("Meno: "); let vek = prompt("Vek: "); ``` ### Úlohy 9 **9a.** Napíš funkciu `prompt(text: &str) -> String` s flush. Použi ju na prečítanie mena a veku. **9b.** Napíš funkciu `prompt_cislo(text: &str) -> Option` — zobrazí text, prečíta, parsne. **9c.** Napíš interaktívny program, ktorý používa prompt funkciu na čítanie príkazov v cykle. --- ## Kapitola 10: Prehľad a vzory ### Rýchly prehľad: čo kedy použiť | Chcem... | Použi | |----------|-------| | Čítať celý súbor | `fs::read_to_string(cesta)` | | Zapísať celý súbor | `fs::write(cesta, obsah)` | | Čítať po riadkoch (malý súbor) | `fs::read_to_string` + `.lines()` | | Čítať po riadkoch (veľký súbor) | `BufReader::new(File::open)` + `.lines()` | | Pripísať na koniec | `OpenOptions::new().append(true)` | | Čítať z konzoly | `io::stdin().read_line(&mut buf)` | | Prompt bez newline | `print!()` + `io::stdout().flush()` | | JSON: súbor → štruktúra | `fs::read_to_string` + `serde_json::from_str` | | JSON: štruktúra → súbor | `serde_json::to_string_pretty` + `fs::write` | | Testovateľný I/O | `trait IOManager` + `impl IOManager for ...` | ### Vzor: kompletné menu programu ```rust use std::io::{self, Write}; use std::path::PathBuf; fn prompt(text: &str) -> String { print!("{}", text); io::stdout().flush().unwrap(); let mut vstup = String::new(); io::stdin().read_line(&mut vstup).unwrap(); vstup.trim().to_string() } fn main() { let cesta = PathBuf::from("data.json"); let mut kniznica = Kniznica::nacitaj_zo_suboru(&cesta) .unwrap_or_default(); loop { println!("\n--- KNIŽNICA ---"); println!("1. Pridaj knihu"); println!("2. Vypíš knihy"); println!("3. Hľadaj"); println!("4. Koniec"); let volba = prompt("Vyber: "); match volba.as_str() { "1" => { let nazov = prompt("Názov: "); let autor = prompt("Autor: "); // ... pridaj knihu kniznica.uloz_do_suboru(&cesta); println!("Pridané."); } "2" => { for kniha in &kniznica.knihy { println!("{}", kniha); } } "3" => { let vyraz = prompt("Hľadaj: "); // ... hľadaj } "4" => { println!("Zbohom!"); break; } _ => println!("Neznáma voľba"), } } } ``` ### Úlohy 10 **10a.** Napíš kompletný interaktívny program pre TODO list: - Pridaj úlohu (prompt názov) - Označ ako hotovú (prompt index) - Vypíš všetky - Ulož do JSON - Koniec **10b.** Napíš program, ktorý prečíta CSV súbor (meno,vek,mesto), sparsuje riadky do Vec štruktúr, a umožní hľadanie podľa mesta. **10c.** Implementuj kompletný IOManager trait pre hru Obesenec. Vytvor aj TestIOManager, ktorý automaticky „hrá" hru s predpripravenými písmenami.