Cél és előfeltétel
Egy könyvtári felület gyakran más alakban kap adatot, mint ahogyan belül használja. A newtype-gyakorlat már megmutatta az érvényes állapot határát. Most az oda vezető átalakításokat nevezzük meg. A cél, hogy a hívás típusából kiderüljön: egyszerű értékátadásról vagy valóban elutasítható bemenetről van szó.
Két külön ígéret
A From<T> egy adott forrástípus minden értékére sikeres átalakítást képvisel. Ha a cél csak egy szűkebb számtartományt enged meg, a forrás minden értékét nem tudod ilyen módon átvenni. Ilyenkor a TryFrom<T> visszatérési típusa ad helyet a hibának. A különbség a hívó döntéseit is meghatározza: az egyik út egy új értéket, a másik egy kezelendő Result eredményt ad.
A konverzió nem alkalmas arra, hogy a veszteséget elrejtsd. Egy magas bitszámú szám csonkítása, egy meg nem nevezett kerekítés vagy egy hibás azonosító alapértékre cserélése meglepetést okozna. Írd le előre, mi marad változatlan, és mely bemenetek elfogadottak. A From dokumentációja a hibamentes és jelentésében kézenfekvő átalakítást hangsúlyozza.
A típus és a hiba együtt alkot felületet
A TryFrom társított Error típusa lehet kicsi felsorolás, ha a hívónak csak néhány stabil eset között kell döntenie. A szöveges hibaüzenet önmagában kevésbé alkalmas programozott elágazásra. A feladatokban ezért külön hibaváltozat jelzi a rossz számformátumot és az érvényes számhoz képest hibás tartományt. A két helyzet eltérő javítást igényel a bemenet előállítójától.
A From megvalósítása után az azonos konverziós irányt kifejező Into használatot a standard könyvtár biztosítja. Általában a helyi céltípusra írj From megvalósítást, és a hívó használhassa a számára olvashatóbb alakot. A cél típusa az into() hívásnál nem mindig következtethető ki; ilyenkor egy típusjelölés segít. Ugyanez a gondolat a TryFrom és TryInto párnál is megjelenik.
A példák új, különálló programok. A belső mezők itt az adott program megvalósításához tartoznak; nyilvános könyvtárban az előző lecke modulhatárát is tartsd meg. A konverziós trait nem helyettesíti az adatrejtést. Ne kínálj ugyanarra az irányra egyszerre hibamentes és hibázható, egymásnak ellentmondó átalakítást.
A konverzió irányát a hívó szemszögéből is olvasd végig. A kilométerből méterbe váltás itt pontos, mert a cél nagyobb számtartományt ad. A visszafelé vezető út viszont már felvetné, mi történjen az ezerrel nem osztható értékekkel. Ez új szerződés lenne, amelyet nem szabad a meglévő irányból kikövetkeztetni. Ugyanígy egy sikeresen beolvasott egész szám még nem feltétlenül használható mennyiség. A szintaktikai olvasás és a tartományellenőrzés külön lépése segít olyan hibaüzenetet adni, amelyből a felhasználó tudja, mit kell javítania. A tesztjeid ezt a különbséget is őrizzék.
1. feladat: egységváltás elegendő céltartománnyal
A Kilometres(u32) értékből készíts méterben megadott u64 számot. A legnagyobb u32 kilométerérték ezerszerese is elfér a választott céltípusban, ezért ez a konverzió mindig sikeres lehet. A szorzás előtt növeld meg a szám típusát. A kis példák mellett a legnagyobb forrásértékkel is igazold ezt a sorrendet.
Megoldás
struct Kilometres(u32);
impl From<Kilometres> for u64 {
fn from(value: Kilometres) -> Self { u64::from(value.0) * 1000 }
}
fn main() {
let metres: u64 = Kilometres(3).into();
assert_eq!(metres, 3000);
assert_eq!(u64::from(Kilometres(0)), 0);
assert_eq!(u64::from(Kilometres(u32::MAX)), 4_294_967_295_000);
}
A u32 tartományban végzett szorzás és az utólagos bővítés más műveletsor lenne. A teszt ezért az átalakítás lépéseinek helyességét is védi.
2. feladat: százalék, hibával
Készíts Discount típust, amely i16 forrásból csak nulla és száz közötti értéket enged meg. A két szélső pont érvényes, a negatív és száz fölötti érték hibás. Ne alakítsd a hibás értéket nullává. A hívó kapjon külön típust, amelyen egyértelműen látszik a sikertelen konverzió.
Megoldás
#[derive(Debug, PartialEq, Eq)]
struct Discount(u8);
#[derive(Debug, PartialEq, Eq)]
struct OutOfRange;
impl TryFrom<i16> for Discount {
type Error = OutOfRange;
fn try_from(n: i16) -> Result<Self, Self::Error> {
if (0..=100).contains(&n) { Ok(Self(n as u8)) }
else { Err(OutOfRange) }
}
}
fn main() {
assert_eq!(Discount::try_from(0), Ok(Discount(0)));
assert_eq!(Discount::try_from(100), Ok(Discount(100)));
assert_eq!(Discount::try_from(-1), Err(OutOfRange));
assert_eq!(Discount::try_from(101), Err(OutOfRange));
}
A szűkítő átalakítás csak a sikeres tartományvizsgálat után történik meg. Így a as művelethez ezen a ponton már ismert, szűk feltétel tartozik.
3. feladat: szöveges darabszám két hibahatárral
A Units egy és száz közötti darabszámot tároljon. A szövegnek közvetlenül u16 számmá alakíthatónak kell lennie; ne vágj whitespace karaktereket. A „sok” formátumhiba, a „0” tartományhiba, a „65536” ismét formátumhiba a választott számtípus számára. Ezt a három helyzetet külön próbákkal rögzítsd.
Megoldás
#[derive(Debug, PartialEq, Eq)]
struct Units(u16);
#[derive(Debug, PartialEq, Eq)]
enum Conversion { Number, Range }
impl TryFrom<&str> for Units {
type Error = Conversion;
fn try_from(s: &str) -> Result<Self, Self::Error> {
let n = s.parse::<u16>().map_err(|_| Conversion::Number)?;
if (1..=100).contains(&n) { Ok(Self(n)) }
else { Err(Conversion::Range) }
}
}
fn main() {
assert_eq!(Units::try_from("1"), Ok(Units(1)));
assert_eq!(Units::try_from("100"), Ok(Units(100)));
assert_eq!(Units::try_from("0"), Err(Conversion::Range));
for bad in ["sok", " 2", "65536", ""] {
assert_eq!(Units::try_from(bad), Err(Conversion::Number));
}
}
Az első hiba rögtön visszatér, ezért a tartományvizsgálat csak valódi u16 értékkel találkozik. A hibák sorrendje a felület szerződésének része.
A hívónak a konverzió eredményével kell döntést hoznia: hibánál ne folytassa a munkát úgy, mintha ellenőrzött értéket kapott volna.
Elfogadási feltételek
- A kilométerkonverzió nullánál és a legnagyobb forrásértéknél is a pontos méterértéket adja.
- A százaléktípus a két szomszédos hibás határértéket elutasítja, és nem helyettesíti őket sikeres alapértékkel.
- A szöveges mennyiség formátum- és tartományhibája különbözik, és a whitespace kezelését a teszt rögzíti.
Továbblépés
A konverzió útja most a hibalehetőséget is megnevezi. A builder-leckében több, külön időben megadott mezőt fogunk egyetlen ellenőrzött objektummá alakítani. A TryFrom szabályait később is megtarthatod az egyes mezők belépési pontjain.