A programok életében keletkezhetnek olyan helyzetek, amikor egy kérést nem tudnak végre hajtani. Ha például egy API-t fejlesztünk, ami kapcsolódik egy adatbázishoz, akkor előfordulhat, hogy az adatbázis szerver éppen nem elérhető. Ilyen helyzetekben hibakezelés nélkül az egyetlen lehetőségünk a pánik, ami rontja a felhasználói élményt és váratlan állapotokat teremthet a rendszerünkben, mivel a program futásának azonnali megszakításával jár.
Az egyik kedvenc funkcióm a rust nyelvben, hogy jelentősen megkönnyíti a hibakezelést. A nyelv kifejezőerejénél fogva világosan rámutat azokra a pontokra, ahol kivételek keletkezhetnek.
Tegyük fel, hogy szeretném inicializálni az adatbázis kapcsolatokat a programomban. Ezt megtehetem a PgPoolManager
-em
new
függvényével:
1impl PgPoolManager {
2 pub async fn new(
3 main_database_config: &BasicDatabaseConfig,
4 default_tenant_database_config: &BasicDatabaseConfig,
5 ) -> Result<PgPoolManager, sqlx::Error> {
6 let main_pool = PgPoolOptions::new()
7 .max_connections(main_database_config.max_pool_size())
8 .acquire_timeout(Duration::from_secs(3))
9 .connect(&main_database_config.url())
10 .await?;
11 let default_tenant_pool = PgPoolOptions::new()
12 .max_connections(default_tenant_database_config.max_pool_size())
13 .acquire_timeout(Duration::from_secs(3))
14 .connect(&default_tenant_database_config.url())
15 .await?;
16 Ok(Self {
17 main_pool,
18 default_tenant_pool,
19 tenant_pools: Arc::new(RwLock::new(HashMap::new())),
20 })
21 }
22}
A rust típusrendszere miatt a függvény szignatúrájából látszik, hogy ez egy olyan művelet, ami bizonyos esetekben meghiúsulhat, mert a függvény visszatérési értéke a standard library Result típusába van csomagolva. (Ami az eredeti kódban egy anyhow::Result, de ez majd egy későbbi bejegyzés témája lesz :))
Ezekkel az információkkal felvértezve a fejlesztő tudja, hogy ezt a függvényt csak úgy hívhatja meg, ha megírja hozzá a hibakezelést is.
Ebben a példában, ha nem sikerül csatlakozni az adatbázishoz, a program értesíti az adminisztrátort a hibáról a felhasználónak pedig kiírni egy szépen formázott üzenetet, ami közli ezt a tényt:
1
2#[tokio::main]
3async fn main(config: Arc<AppConfig>) -> Result<(), String> {
4 let config = init_config()?;
5 let pool_manager = PgPoolManager::new(
6 config.main_database(),
7 config.default_tenant_database()
8 ).await;
9
10 match pool_manager {
11 Ok(pool_manager) => {
12 pool_manager.some_useful_stuff();
13 Ok(())
14 }
15 Err(e) => {
16 error_notify_admin(e);
17 Err(String::from("Nem sikerült csatlakozni az adatbázishoz"))
18 }
19 }
20}
Csak egy kis pánik Link to heading
Sok más programozási nyelvvel ellentétben a rust alapértelmezetten megköveteli a hibakezelést, de ad lehetőséget arra, hogy explicit azt mondjam neki, hogy szeretném kihagyni azt. Ezt az .unwrap() függvénnyel tudom megtenni, ami jól látható módon jelzi a forráskódban, hogy ez történt és szomorú kiskutya szemekkel néz rád, amíg le nem kezeled a hibát.
A .unwrap()
használata production-ben erősen ellenjavallott, mert ha error ágra fut, akkor a program azonnal eldob
mindent a kezéből és leáll (vagy legalább az éppen futó szálat eldobja anélkül, hogy a felhasználót tájékoztatná hogy
mi történt)