This commit is contained in:
Priec
2026-03-05 21:08:21 +01:00
parent 508c97cc9c
commit aed169f828

820
priprava/rust_priprava13.md Normal file
View File

@@ -0,0 +1,820 @@
# 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!("{}{} 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<usize, io::Error> (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<i32> {
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<char> {
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<String>` — prečíta riadok, trimne, vráti None ak je prázdny.
**2c.** Napíš funkciu `nacitaj_float() -> Option<f64>` — 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 <meno>");
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<String, std::io::Error> = 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<String> {
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<u8> = fs::read("obrazok.png").unwrap();
println!("Veľkosť: {} bajtov", bajty.len());
```
### Úlohy 4
**4a.** Napíš funkciu `pocet_riadkov(cesta: &str) -> Option<usize>` — prečíta súbor a vráti počet riadkov.
**4b.** Napíš funkciu `najdi_v_subore(cesta: &str, hladany: &str) -> Vec<String>` — vráti riadky, ktoré obsahujú hľadaný text.
**4c.** Napíš funkciu `nacitaj_cisla(cesta: &str) -> Option<Vec<i32>>` — 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<String> {
std::fs::read_to_string(cesta).ok()
}
// Lepšie: berie &Path — funguje s PathBuf aj &str
fn nacitaj(cesta: &Path) -> Option<String> {
std::fs::read_to_string(cesta).ok()
}
// fs::read_to_string berie impl AsRef<Path> — 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<Kniha>,
}
#[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<Kniznica> {
// 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<Todo>,
}
#[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<i32>`. Implementuj:
- `KonzolaVstup` — číta z stdin
- `TestovaciVstup { cisla: Vec<i32>, 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<char>,
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<i32>` — 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.