ראסט - פונקציית main נקיה יותר עם Result

כחלק מהמסע שלנו לניהול שגיאות נכון נפל לנו האסימון שלהשתמש ב unwrap או expect יכול להיות רעיון לא כל כך טוב ועלול להביא לסיום מיידי של הפרוסס שלנו במקרים שאולי לא התכוונו אליהם. אז השתמשנו ב clippy deny unwrap used שגרם לקומפילציה לא לעבור במידה ונכנס unwrap לקוד.
המקום היחיד בו היינו צריכים להשאיר unwrap היה בסט הוולידציות ההתחלתי בעליה של הפרוסס שלנו, בפונקציית ה main. לדוגמה - בניסיון למשוך את הקונפיגורציה הראשונית בעליה - אם נכשלנו מסיבה כלשהי נהרוג את הפרוסס.
לימים פונקציית ה main הכילה יותר ויותר ולידציות כאלה ובנוסף גם התחלנו להרוג את הפרוסס שלנו כחלק מתהליך פונקציונאלי תקין במקרים מסויימים (exit code).
בפוסט אנסה להציג את האלטרנטיבה שבחרנו לטיפול בשגיאות ב main באמצעות unwrap
איך נראה main סטנדרטי
use std::fs::File;
use std::io::Read;
fn main() {
// Read the configuration file, expecting it to succeed
let config = read_config("config.toml").expect("Failed to read config");
// Process the data, unwrapping the result
process_data(&config).expect("Failed to process data");
}
fn read_config(filename: &str) -> infra::Result<String> {
let mut file = File::open(filename)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
Ok(contents)
}
fn process_data(config: &str) -> infra::Result<()> {
// Simulate data processing
if config.contains("error") {
Err("Simulated processing error".into())
} else {
println!("Data processed successfully.");
Ok(())
}
}
הפריע לי בעין לראות כל כך הרבה unwrap וחיפשתי דרך נקייה יותר לנהל את השגיאות שעלולות לצוץ ב main. באחת מעשרות השיחות הטכניות שלי על ראסט עם המנטור שלי, הוא העלה את האופציה פשוט להחזיר result מפונקציית ה main וככה אם תוחזר שגיאה ב main היא תפועפע שכבה אחת והתכנית תסגר עם אותה הודעת השגיאה (ממש עם unwrap).
main שמחזיר result
fn main() -> infra::Result<()> {
// Read the configuration file
let config = read_config("config.toml")?;
// Process the data
process_data(&config)?;
Ok(())
}
דבר קטן נוסף - main לא חייב להחזיר “()” או unit בהגה המקצועית. במקרים בהם הפרוסס שלנו יכול לצאת במגוון exit codes שונים, נוכל להגדיר ש main יחזיר std::process::ExitCode .
fn main() -> infra::Result<ExitCode> {
// Read the configuration file
let config = read_config("config.toml")?;
// Process the data
let exit_code = process_data(&config)?;
Ok(exit_code)
}
סיכום
קריאות ל unwrap מתוך פונקציית ה main הן לא דבר שגוי כאשר אנחנו באמת רוצים לקרוס בהינתן שגיאה. הפוסט מציע דרך אלטרנטיבית שאני מצאתי ״נקייה״ יותר - החזרת Result מפונקציית ה main ואפשרות לפעפע שגיאות באמצעות ? כמו שאנחנו רגילים לראות בפונקציות הרגילות שלנו.