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!("{} 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
// 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ý:
- Načíta TodoList z "todo.json"
- Pridá novú úlohu
- Uloží späť
- 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 stdinTestovaciVstup { 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.