Cél és előfeltétel

A parancssori eszközök egyik erőssége, hogy összekapcsolhatók. Egy program eredménye a másik bemenetévé válhat, ha a kimenet nem keveredik össze véletlen állapotüzenetekkel. Ebben a leckében sorokat olvasol és külön kezeled a rendes eredményt a diagnosztikától. A fő kérdés: milyen adatot vár a következő program ugyanazon a csatornán?

Előfeltétel az argumentumok és használati útmutató, valamint a szeletek és nézetek. A teljes edition 2024 példák memóriabeli bemenetet használnak. Nem várnak gépelésre, így minden futásuk ugyanolyan eredményt ellenőriz. A valódi standard bemenet ehhez hasonló olvasási felületet ad, de érkezésének időzítését a felhasználó vagy másik folyamat határozza meg.

A csatornák szerepét tartsd külön

A standard bemenet a bejövő adat alapértelmezett csatornája. A standard kimenetre kerül az eszköz hasznos eredménye. A standard hibakimenet a hibamagyarázatok és szükséges diagnosztika helye. A két kimenet terminálban gyakran egymás mellett látszik, de átirányításkor elválasztható. Emiatt egy sor elé írt „feldolgozás indul” üzenet tönkreteheti a másik program által olvasott adatfolyamot.

A felhasználó által kért súgó viszont lehet rendes, sikeres kimenet. Hibás argumentum esetén a magyarázat és rövid használati emlékeztető a hibakimenetre illik. Nincs minden programra érvényes egyetlen formátum, de a saját eszközöd szerződésének következetesnek kell lennie. A példáinkban két külön pufferrel ellenőrizzük, melyik szöveg melyik helyre került.

Egy sor olvasása állapotot módosít

A read_line a megadott String végéhez fűzi az olvasott adatot, és siker esetén a beolvasott bájtok számát adja vissza. Ha ugyanazt a puffert új sorhoz használod, és különálló sort szeretnél, előbb ürítened kell. A sorvég is benne maradhat a pufferben. Ezért a két egymás utáni olvasást nem elég úgy elképzelni, mint egy változó automatikus lecserélését.

A sikeresen visszaadott nulla bájt az adatfolyam végét jelezheti, nem egy külön üres sort. Egy üres sorhoz ugyanis sorvég tartozik, amelynek van bájthossza. A különbséget a feladatban két sor után még egy olvasással ellenőrizzük. Nem az üres String tartalmából találgatjuk önmagában, hogy a forrás véget ért-e.

Tesztelhető I/O felület

A Cursor egy memóriabeli bájtsorozatot kínál olvasható forrásként. Emiatt ugyanazt az olvasási műveletet tudod kipróbálni, amelyet később fájlon vagy standard bemeneten használnál. A kimenet pedig írható bájtvektor lehet. A függvényekben szereplő impl BufRead és impl Write azt mondja: a konkrét tároló helyett a szükséges olvasási vagy írási képességet kérjük.

A kérdőjel a Result hibáját továbbadja a hívónak, nem hallgatja el. Olvasás és írás is hibázhat, ezért az eredményt vissza kell vezetni a függvény szerződésébe. A memóriabeli sikeres példa nem igazol minden terminál- vagy fájlrendszerhibát; azt igazolja, hogy az adat és a formátum a megadott bemeneten megfelelő. A külső környezet majd további ellenőrzést kíván.

1. feladat: két sor, majd a forrás vége

Olvass két sort egy Cursor értékből. Minden olvasás előtt ürítsd a közös puffert, és ellenőrizd a teljes tartalmat a sorvéggel együtt. A harmadik olvasás már nullát adjon. Így egyszerre látszik a puffer újrafelhasználása és az adatfolyam végét jelző eredmény. A visszaadott bájtszámot se hagyd figyelmen kívül.

Megoldás
use std::io::{self, BufRead, Cursor};
fn main() -> io::Result<()> {
    let mut bemenet = Cursor::new(b"alma\nkorte\n");
    let mut sor = String::new();
    assert_eq!(bemenet.read_line(&mut sor)?, 5);
    assert_eq!(sor, "alma\n");
    sor.clear();
    assert_eq!(bemenet.read_line(&mut sor)?, 6);
    assert_eq!(sor, "korte\n");
    sor.clear();
    assert_eq!(bemenet.read_line(&mut sor)?, 0);
    assert!(sor.is_empty());
    Ok(())
}

Az itt választott szövegekben minden betű egy bájt. A bájtszám a sorvéget is tartalmazza, tehát nem pusztán a látható betűk számát ellenőrizzük.

2. feladat: nem üres sorok továbbírása

Írj feldolgozót, amely soronként olvas, a teljesen üres sorokat kihagyja, a többit sorvéggel együtt a kimenetre írja. A szóközös sort ne töröld: ebben a szabályban az nem üres. Használd a lines műveletet, amely a sorvégi lezárást leválasztja. Ellenőrizd a kimeneti bájtokat, a továbbírt sorok számát és az üres forrást is.

Megoldás
use std::io::{self, BufRead, Cursor, Write};
fn masol(bemenet: impl BufRead, kimenet: &mut impl Write) -> io::Result<usize> {
    let mut darab = 0;
    for sor in bemenet.lines() {
        let sor = sor?;
        if sor.is_empty() { continue; }
        writeln!(kimenet, "{sor}")?;
        darab += 1;
    }
    Ok(darab)
}
fn main() -> io::Result<()> {
    let mut kimenet = Vec::new();
    assert_eq!(masol(Cursor::new(b"alma\n\n \nkorte"), &mut kimenet)?, 3);
    assert_eq!(kimenet, b"alma\n \nkorte\n");
    let mut ures = Vec::new();
    assert_eq!(masol(Cursor::new(b""), &mut ures)?, 0);
    assert!(ures.is_empty());
    Ok(())
}

Az utolsó bemeneti sor lezárás nélkül is feldolgozható. A kimeneti szabályunk minden továbbírt sorhoz egy sorvéget ad, így ez tudatos formázás, nem bájtpontos másolás.

3. feladat: eredmény és hiba külön pufferben

Készíts függvényt, amely sikeres esetben az „eredmény” sort írja a rendes kimenetre. Hibás esetben csak a „Hiba: hibás kérés” sor kerüljön a másik pufferbe. Mindkét helyzetben ellenőrizd mindkét csatornát, mert egy helyes üzenet önmagában nem bizonyítja, hogy nem szivárgott mellé adat a rossz helyre.

Megoldás
use std::io::{self, Write};
fn jelent(ok: bool, ki: &mut impl Write, hiba: &mut impl Write) -> io::Result<()> {
    if ok { writeln!(ki, "eredmény")?; }
    else { writeln!(hiba, "Hiba: hibás kérés")?; }
    Ok(())
}
fn main() -> io::Result<()> {
    let (mut ki, mut hiba) = (Vec::new(), Vec::new());
    jelent(true, &mut ki, &mut hiba)?;
    assert_eq!(ki, "eredmény\n".as_bytes());
    assert!(hiba.is_empty());
    ki.clear();
    jelent(false, &mut ki, &mut hiba)?;
    assert!(ki.is_empty());
    assert_eq!(hiba, "Hiba: hibás kérés\n".as_bytes());
    Ok(())
}

A példában nem a valódi terminált figyeljük. Az elkülönített pufferek egyértelműen, megismételhetően bizonyítják a függvény csatornaválasztását.

Elfogadási feltételek

  • A két read_line hívás után a puffer az adott sort tartalmazza, a harmadik olvasás nulla bájttal véget jelez.
  • A feldolgozó csak a teljesen üres sorokat hagyja ki, és egységesen sorvéget tesz a többihez.
  • Siker esetén a hibapuffer, hiba esetén az eredménypuffer üres marad.

Továbblépés

A feldolgozó már elkülönült az adat konkrét forrásától. A fájlok és útvonalak leckében ezt a szerződést a fájlrendszer fogalmaival kapcsolod össze, az útvonal építését és a tényleges olvasást külön vizsgálva.