Files
JR-priprava-na-skusku/priprava/rust_priprava13.md
Priec aed169f828 io
2026-03-05 21:08:21 +01:00

21 KiB

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

// 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

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

// 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

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

// 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

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

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!

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

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

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

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

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:

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

// 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

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)

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

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.

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

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

// 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.

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

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

// 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:

#[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?

// 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

// 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

// 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:

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:

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:

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ď.

// 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

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.