Cél és előfeltétel
Ebben a leckében egy egyszerű szövegfeldolgozó eszköz részleteit készíted el: adatot olvasol egy szövegből, rövid változatot állítasz elő, majd egy új feliratot építesz. A cél, hogy a függvényeid magyar ékezetek mellett is kiszámíthatóan viselkedjenek. A megoldások kizárólag a standard könyvtárat használják, külső csomag nélkül.
Előfeltétel az A Rust alapjai útvonal, az Option ismerete és a Vec és slice gyakorlat elvégzése. Különösen a kölcsönzésre figyelj: a szöveget gyakran csak megvizsgáljuk, miközben a tulajdonosa továbbra is használni szeretné. Nyiss egy Rust 2024 editiont használó projektet, és a teljes példákat külön-külön tedd a src/main.rs fájlba.
Mikor String, mikor &str?
A String saját szöveget tárol, amelyet bővíthetsz vagy módosíthatsz. A &str egy már létező, érvényes UTF-8 szövegrészlet kölcsönzött nézete. Ha egy függvény csak olvas, a &str paraméter kényelmes választás: szövegliterált és egy String tartalmát is átadhatod neki. Ezzel a hívó megtarthatja az adat tulajdonjogát.
Ha új eredményt építesz, többnyire String legyen a visszatérési érték. Ha az eredmény a bemenet egy változatlan része, sokszor elegendő egy &str. A következő példa mindkettőt megmutatja. A trim levágja a szélső whitespace karaktereket, a format! pedig új szöveget készít. A köztes kölcsönzés után az eredeti változó újra módosítható, mert a kölcsönzött nézetet már nem használjuk.
fn main() {
let mut nev = String::from(" Réka ");
let tiszta: &str = nev.trim();
let felirat = format!("Szia, {tiszta}!");
assert_eq!(felirat, "Szia, Réka!");
nev.push('!');
assert_eq!(nev, " Réka !");
}
Ne kérj tulajdonolt String értéket pusztán azért, mert a hívónál éppen ilyen típusú változó van. A függvény szerződését az határozza meg, hogy mit végez az adattal. Egy keresés vagy egy hosszvizsgálat rendszerint olvasási művelet; egy új címke elkészítése viszont saját eredményt ad.
Három különböző hossz
A szöveg bájtok sorozataként tárolódik, ezért a len() bájthosszt jelent. A chars() Unicode-skalárértékeket jár be. Ez a két mérés már az á betűnél eltér. A képernyőn egyetlen jelnek érzékelt graféma pedig akár több skalárértékből is állhat: ilyen az alapbetűből és kombináló ékezetből összeállított betű, vagy több emoji-összetevő együttese.
fn main() {
let szo = "árvíz";
assert_eq!(szo.len(), 7);
assert_eq!(szo.chars().count(), 5);
assert_eq!(szo.get(..1), None);
assert_eq!(szo.get(..2), Some("á"));
let egyben = "é";
let kombinacioval = "e\u{301}";
assert_eq!(egyben.chars().count(), 1);
assert_eq!(kombinacioval.chars().count(), 2);
assert_ne!(egyben, kombinacioval);
assert_eq!("👩💻".chars().count(), 3);
}
A standard könyvtár nem ad általános grafémabontást vagy Unicode-normalizálást. A hasonlóan megjelenő két é alak tehát itt különböző szöveg marad. A chars() megszámolása nem alkalmas általános „látható karakter” korlát megvalósítására. Ezt a különbséget a Rust Book szövegkezelési fejezete is részletesen bemutatja.
A get paraméterében szereplő tartomány szintén bájtpozíciót használ. Ha a határ egy több bájtos skalárérték belsejébe esik, az eredmény None. A közvetlen szeletelés ilyen helyzetben pánikolna, ezért külső adatból számolt határnál a biztonságos str::get hasznosabb. Most inkább bejárással dolgozunk, és a feladataink pontosan kimondják, milyen egységet kezelnek.
1. feladat: rövidítés skalárértékek szerint
Írj rovidit függvényt, amely az első megadott számú Unicode-skalárértékből új String értéket készít. Ne tegyen hozzá három pontot, és az eredeti szöveget hagyja meg. Az üres bemenet, a nulla korlát és a szövegnél nagyobb korlát is legyen szabályos eset. Előbb fogalmazd meg a várt eredményt a kombináló ékezetes példára is: egyetlen skalárérték kérésére az alapbetű marad.
Megoldás
fn rovidit(szoveg: &str, limit: usize) -> String {
szoveg.chars().take(limit).collect()
}
fn main() {
let eredeti = String::from("árvíz");
assert_eq!(rovidit(&eredeti, 3), "árv");
assert_eq!(eredeti, "árvíz");
assert_eq!(rovidit("Rust", 2), "Ru");
assert_eq!(rovidit("", 5), "");
assert_eq!(rovidit("Rust", 0), "");
assert_eq!(rovidit("á", usize::MAX), "á");
assert_eq!(rovidit("e\u{301}", 1), "e");
}
A take legfeljebb a megadott számú elemet engedi tovább. A collect összegyűjti a skalárértékeket egy új szövegbe. Így nem keletkezik hibás UTF-8, de a graféma szétszakadása továbbra is lehetséges: ezt a függvény neve mellett a szerződésben is rögzítettük.
2. feladat: az első szó kölcsönzése
Készíts elso_szo függvényt, amely az első whitespace által elválasztott szövegrészletet adja vissza másolás nélkül. Az írásjeleket őrizze meg: a Rust, egyetlen token. Csak whitespace esetén ne találj ki üres szót, hanem adj None értéket. Gondold végig, miért nem használható a visszaadott nézet a bemenet megszűnése után.
Megoldás
fn elso_szo(szoveg: &str) -> Option<&str> {
szoveg.split_whitespace().next()
}
fn main() {
let szoveg = String::from(" árvíz\nRust");
assert_eq!(elso_szo(&szoveg), Some("árvíz"));
assert_eq!(szoveg, " árvíz\nRust");
assert_eq!(elso_szo("Rust, jó"), Some("Rust,"));
assert_eq!(elso_szo("\t\n "), None);
assert_eq!(elso_szo(""), None);
}
Az Option<&str> egyszerre jelzi a találat hiányát és azt, hogy a sikeres eredmény kölcsönzött. Itt a tokenizálás whitespace szerinti felosztás, nyelvészeti szófelismerést nem végzünk.
3. feladat: rendezett bemenetből felirat
Építs cimke függvényt névből és témából. Mindkét bemenet széléről vágd le a whitespace karaktereket. Üres név helyett Névtelen, üres téma helyett Rust jelenjen meg, közöttük pedig középpont legyen. Az eredménynek önállóan használhatónak kell maradnia, ezért saját String értéket adjon vissza.
Megoldás
fn cimke(nev: &str, tema: &str) -> String {
let nev = nev.trim();
let tema = tema.trim();
let nev = if nev.is_empty() { "Névtelen" } else { nev };
let tema = if tema.is_empty() { "Rust" } else { tema };
format!("{nev} · {tema}")
}
fn main() {
assert_eq!(cimke(" Réka ", " Unicode "), "Réka · Unicode");
assert_eq!(cimke("", ""), "Névtelen · Rust");
assert_eq!(cimke("\t", " "), "Névtelen · Rust");
assert_eq!(cimke("Áron", "CLI"), "Áron · CLI");
}
A felirat építése előtt külön kezeltük az alapértelmezéseket, így a formázási sor egyszerű maradt. A bemeneteket nem módosítjuk; az új eredmény elkészítése után nincs rájuk szükség.
Elfogadási feltételek
- A rövidítés kezeli az üres, nulla és túl nagy korlátos esetet; a kombináló ékezetes tesztje egyértelműen skalárértékeket ellenőriz.
- Az első szó keresése üres bemenetre
Noneértéket ad, és az eredetiStringa hívás után változatlanul használható. - A címke mindkét hiányzó mezőre a megadott alapértéket adja; az összes bemutatott
assertsikeresen lefut.
Összefoglaló és továbblépés
A szövegkezelési döntés két kérdéssel kezdődik: ki birtokolja az adatot, és milyen egységet tekintünk egy elemnek? A &str jól szolgál olvasásra, a String új eredményekhez, a chars() pedig skalárértékek bejárására. A következő leckében a whitespace szerint kapott tokenekből gyakorisági táblát készítünk, és minden bemeneti szabályt ugyanolyan pontosan rögzítünk.