Cél és előfeltétel
Ebben a leckében egy kis könyvtár adatait modellezed. A végére el tudod dönteni, mikor tartoznak össze mezők egy structban, és mikor érdemes egymást kizáró állapotokat enummal kifejezni. Nem adatbázist készítünk: a cél egy olyan memóriabeli modell, amelynek jelentése a függvények aláírásából és a típusokból is olvasható.
Előfeltétel A Rust alapjai elvégzése, különösen az ownership és move megértése. Szükséged lesz függvényekre, változókra és egyszerű elágazásokra. A példákat külön-külön másold egy edition 2024 projekt src/main.rs fájljába, majd futtasd a cargo run parancsot. Az assert hibája nem dísz: azt jelzi, hogy egy elvárt viselkedés nem teljesül.
Először a kérdéseket tisztázd
Egy könyvről tudni szeretnénk a címét és azt, hogy kikölcsönözték-e. Kölcsönzéskor az olvasó neve is szükséges. Ha külön szabad és kolcsonozve logikai mezőt használnánk, mindkettőt igazra is állíthatnánk. A program ekkor olyan állapotot tárolna, amelynek nincs értelmes jelentése. Egy harmadik, mindig jelen lévő olvasónév tovább növelné az ellenőrizendő kombinációk számát.
A struct az összetartozó adatokat fogja össze, az enum pedig felsorolja az alternatívákat. A szabad könyvhöz nem tartozik olvasónév; a kölcsönzött változat viszont hordozza azt. Ettől még a név lehet üres szöveg: a típusok itt a szerkezetet rendezik, nem minden üzleti szabályt ellenőriznek automatikusan. Az üres nevek kezelését külön megírjuk.
#[derive(Debug, PartialEq, Eq)]
enum Allapot {
Szabad,
Kolcsonozve { olvaso: String },
}
#[derive(Debug)]
struct Konyv {
cim: String,
allapot: Allapot,
}
fn leiras(konyv: &Konyv) -> String {
match &konyv.allapot {
Allapot::Szabad => format!("{}: elérhető", konyv.cim),
Allapot::Kolcsonozve { olvaso } => {
format!("{}: kölcsönző: {olvaso}", konyv.cim)
}
}
}
fn main() {
let szabad = Konyv {
cim: String::from("A Pál utcai fiúk"),
allapot: Allapot::Szabad,
};
let foglalt = Konyv {
cim: String::from("Abigél"),
allapot: Allapot::Kolcsonozve {
olvaso: String::from("Eszter"),
},
};
assert_eq!(leiras(&szabad), "A Pál utcai fiúk: elérhető");
assert_eq!(leiras(&foglalt), "Abigél: kölcsönző: Eszter");
println!("{}", leiras(&foglalt));
assert_eq!(foglalt.cim, "Abigél");
}
A match mindkét esetet kezeli. Az olvasó neve csak abban az ágban hozzáférhető, ahol valóban kölcsönzött könyvet kaptunk. Ha később új változatot vezetsz be, a hiányzó ág fordítási hibát okoz. Ez hasznos emlékeztető: a modell bővítése után a viselkedést is újra át kell gondolni.
Megnézéshez kölcsönözz, módosításhoz kérj engedélyt
A leiras paramétere &Konyv, ezért nem veszi át a könyv tulajdonjogát. A hívás után a cím és az állapot továbbra is elérhető. A match &konyv.allapot szintén kölcsönöz; nem próbálja kivenni a String értéket a kölcsönzött könyvből. A visszatérő String új, saját szöveg, amely a könyvtől függetlenül használható.
Olvasás és módosítás külön feladat. Egy állapotot megváltoztató függvénynek &mut Konyv kell, de egy címke elkészítésének nem. Ne adj minden függvénynek módosítható referenciát kényelmi okból. A szűkebb aláírás az olvasónak is megmutatja, hogy mely műveletek hagyják változatlanul az adatot. A Debug a hibakeresést segíti, nem felhasználói felületet tervez helyetted.
A tesztesetek kiválasztásakor ne csak a képernyőre írt szöveget nézd. Kérdezd meg, mi marad igaz a művelet után: ugyanaz a könyv szerepel-e a nyilvántartásban, megmaradt-e a kölcsönző, és elutasításkor változott-e bármi? Egy sikeres és egy sikertelen hívás egymás után gyakran többet árul el, mint két külön, frissen létrehozott rekord. Ezért az utolsó feladatban ugyanazon a könyvön próbálkozunk ismét. A megjelenítő függvényeknél viszont tudatosan több külön példányt használunk, hogy az enum mindegyik ágát ellenőrizzük. Mielőtt lenyitod a megoldást, írd le saját szavaiddal a várt eredményt; utána fordítsd ezt konkrét állítássá.
1. feladat – Különítsd el az adatot a megjelenítésétől
Készíts Kiadvany structot címmel és oldalszámmal. Írj hozzá cimke függvényt, amely kölcsönöz, és például az „Abigél (240 oldal)” szöveget adja vissza. A nulla oldalszámot most ismeretlen terjedelemként engedjük meg: jelenjen meg, ne állítsa le a programot. Ellenőrizd azt is, hogy a címke elkészítése után az eredeti cím olvasható marad.
Megoldás
struct Kiadvany {
cim: String,
oldalak: u16,
}
fn cimke(kiadvany: &Kiadvany) -> String {
format!("{} ({} oldal)", kiadvany.cim, kiadvany.oldalak)
}
fn main() {
let konyv = Kiadvany { cim: String::from("Abigél"), oldalak: 240 };
let ismeretlen = Kiadvany { cim: String::from("Jegyzet"), oldalak: 0 };
assert_eq!(cimke(&konyv), "Abigél (240 oldal)");
assert_eq!(cimke(&ismeretlen), "Jegyzet (0 oldal)");
assert_eq!(konyv.cim, "Abigél");
}
A nulla jelentését előre rögzítettük, ezért az elfogadása itt tudatos döntés. Más alkalmazásban a nullát hibának tekinthetnéd. Az adatmodell nem dönt helyetted; a teszt azt dokumentálja, hogy ebben a feladatban melyik értelmezést választottuk.
2. feladat – Dolgozd fel az enumhoz tartozó adatot
Modellezz két átvételi módot: személyes átvételt és postázást. Csak a postázáshoz tartozzon irányítószám. A leíró függvény mindkét esetben készítsen értelmes szöveget, és ne fogyassza el az átvételi módot. Egy harmadik, valamennyi esetet elfedő match ágat ne használj: szeretnénk észrevenni a későbbi bővítéseket.
Megoldás
#[derive(Debug, PartialEq, Eq)]
enum Atvetel {
Szemelyes,
Posta { iranyitoszam: String },
}
fn leiras(atvetel: &Atvetel) -> String {
match atvetel {
Atvetel::Szemelyes => String::from("Átvétel a könyvtárban"),
Atvetel::Posta { iranyitoszam } => format!("Postázás: {iranyitoszam}"),
}
}
fn main() {
let szemelyes = Atvetel::Szemelyes;
let posta = Atvetel::Posta { iranyitoszam: String::from("0100") };
assert_eq!(leiras(&szemelyes), "Átvétel a könyvtárban");
assert_eq!(leiras(&posta), "Postázás: 0100");
assert_eq!(posta, Atvetel::Posta { iranyitoszam: String::from("0100") });
}
Az irányítószám azonosító, ezért szöveg: a kezdő nulla megmarad. A példa nem ellenőrzi, hogy létezik-e ilyen postai körzet, és nem vállal országfüggetlen címellenőrzést. A változatban tárolás azt biztosítja, hogy személyes átvételhez ne kelljen üres irányítószámot gyártanunk.
3. feladat – Engedj meg egyetlen szabályos állapotváltást
Írj kölcsönzést végző függvényt. Szabad könyvet, nem üres olvasónévvel lehessen kikölcsönözni; a név széléről távolítsd el a szóközöket. A visszatérési érték jelezze, történt-e módosítás. Sikertelen próbálkozáskor a korábbi állapot maradjon meg. Vizsgáld az üres nevet és a második kölcsönzési kísérletet is.
Megoldás
#[derive(Debug, PartialEq, Eq)]
enum Allapot {
Szabad,
Kolcsonozve { olvaso: String },
}
struct Konyv {
allapot: Allapot,
}
fn kolcsonoz(konyv: &mut Konyv, olvaso: &str) -> bool {
let olvaso = olvaso.trim();
if olvaso.is_empty() || !matches!(&konyv.allapot, Allapot::Szabad) {
return false;
}
konyv.allapot = Allapot::Kolcsonozve { olvaso: olvaso.to_owned() };
true
}
fn main() {
let mut konyv = Konyv { allapot: Allapot::Szabad };
assert!(!kolcsonoz(&mut konyv, " "));
assert_eq!(konyv.allapot, Allapot::Szabad);
assert!(kolcsonoz(&mut konyv, " Eszter "));
assert!(!kolcsonoz(&mut konyv, "Bence"));
assert_eq!(konyv.allapot, Allapot::Kolcsonozve {
olvaso: String::from("Eszter"),
});
}
Előbb ellenőrzünk, és csak utána módosítunk. Így az elutasított művelet nem törli a korábbi kölcsönzőt. A bool itt elegendő a változás jelzésére; ha a hiba okát is szeretnénk közölni, beszédesebb visszatérési típusra lesz szükség.
Elfogadási feltételek
- A három megoldás külön-külön lefut, és minden assert teljesül, beleértve a nulla oldalszámot és a kezdő nullás azonosítót.
- A csak olvasó függvények kölcsönöznek; ugyanaz az adat legalább kétszer feldolgozható másolás nélkül.
- Az üres névvel és már kölcsönzött könyvvel végzett próbálkozás nem módosítja az állapotot.
Összefoglaló és továbblépés
A jól megválasztott mezők megnevezik az adatokat, az enum pedig láthatóvá teszi az egymást kizáró eseteket. A műveletek aláírása különbséget tesz megfigyelés és változtatás között. Ez még kis példában is csökkenti a fejben tartandó feltételeket. Következőként az Option, Result és adatellenőrzés leckében a hiányt és a hibát is külön típussal írjuk le, hogy a hívó ne csak egy igaz vagy hamis értékből találgasson.