Funkcionální styl v Rustu: neměnnost, deklarativnost a zpracování chyb
Funkcionální programování v Rustu eliminuje typické problémy imperativního a objektově orientovaného stylu: měnitelnost, imperativní cykly a implicitní zpracování chyb. Místo mutace stavů se kód staví na neměnných transformacích, deklarativních operacích a explicitních typech jako Result a Option. To dělá programy předvídatelnějšími a méně náchylnými k chybám.
Rust je ve výchozím nastavení neměnný, což nutí vývojáře přehodnotit zažité vzory. Funkcionální styl využívá iterátory, kompozici funkcí a pattern matching pro čistý kód bez vedlejších účinků.
Neměnnost místo mutace
Měnitelné proměnné vytvářejí skryté závislosti: hodnota se mění na nečekaných místech, což komplikuje ladění. V imperativním kódu může proměnná mut x změnit hodnotu kdykoli mezi deklarací a použitím.
fn my_function(num: i32) -> i32 {
let mut x = num;
// ...dalších 30 řádků...
x += 96;
// ...dalších 50 řádků...
x
}
Zde se původní hodnota kazí a výsledek je nepředvídatelný kvůli možným mezilehlým změnám. Neměnnost to řeší: vytvářejí se nové hodnoty místo změny starých.
let x = 10;
let new_x = process(x); // x zůstává nezměněno
V Rustu je neměnnost normou. Pro mutaci je potřeba explicitní mut, ale funkcionální styl minimalizuje jeho použití:
- Zkontrolujte, zda lze úlohu řešit neměnně pomocí iterátorů nebo map.
- Pokud je mutace nevyhnutelná, izolujte ji do minimálního bloku.
- Preferujte
clonenebo borrowing pro šíření dat.
To snižuje riziko race conditions a zjednodušuje paralelismus.
Deklarativnost proti imperativnosti
Imperativní kód popisuje kroky provádění: cykly, podmínky, mutace. Funkcionální – požadovaný výsledek. Pro získání čtverců čísel od 0 do 9 imperativní varianta vyžaduje vektor a push:
let mut squares = Vec::new();
for num in 0..10 {
squares.push(num * num);
}
Deklarativní využívá řetězec iterátorů:
let squares: Vec<i32> = (0..10)
.map(|x| x * x)
.collect();
Výhody deklarativního stylu:
- Méně kódu: řetězce iterátorů nahrazují cykly.
- Neměnnost: nedochází k mutacím kolekcí.
- Kompozovatelnost: funkce se snadno kombinují.
- Paralelismus:
.par_iter()pro vícevláknovost bez zámků.
Iterátory v Rustu jsou líné: map se neprovede až do collect nebo for_each, což optimalizuje paměť.
Explicitní zpracování chyb
Výjimky v jiných jazycích maskují chyby: try-catch umožňuje ignorovat typy a kontext. V Rustu Result<T, E> a ? vyžadují explicitní zpracování nebo předání.
Špatný příklad s unwrap:
old_toaster().unwrap(); // Panika při chybě
Správný přístup využívá match nebo map_err:
let toast = match old_toaster() {
Ok(t) => t,
Err(e) => {
eprintln!("Chyba: {}", e);
std::process::exit(1);
}
};
Zkratky pro zpracování:
matchpro plnou kontrolu.map_errpro transformaci chyb.?pouze pokud volající zpracuje.- Knihovny
anyhownebothiserrorpro pohodlí.
To zabraňuje panikám v produkci a nutí přemýšlet o chybách ve fázi kompilace.
Odstranění implicitní prázdnoty
V imperativních jazycích se null skrývá všude, což způsobuje NullPointerException. Rust používá Option<T>: Some(hodnota) nebo None explicitně.
let maybe_candies: Option<Vec<Candy>> = get_candies();
let candies = match maybe_candies {
Some(c) => c,
None => Vec::new(),
};
Pattern matching zaručuje zpracování obou případů. Kompilátor nepustí None.
Co je důležité
- Neměnnost ve výchozím nastavení snižuje chyby ze stavů.
- Iterátory a map nahrazují cykly, což dělá kód deklarativním.
- Result a Option nutí k explicitnímu zpracování chyb a prázdnoty.
- Kompozice funkcí zjednodušuje testování a refaktorování.
- Funkcionální styl se hodí pro 80 % úloh v Rustu; mutace – pouze v IO nebo výkonnostních hotspotech.
— Editorial Team
Zatím žádné komentáře.