כל הפוסטים

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

כחלק מהמסע שלנו לניהול שגיאות נכון נפל לנו האסימון שלהשתמש ב unwrap או expect יכול להיות רעיון לא כל כך טוב ועלול להביא לסיום מיידי של הפרוסס שלנו במקרים שאולי לא התכוונו אליהם.

ראסט - פונקציית 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 ואפשרות לפעפע שגיאות באמצעות ? כמו שאנחנו רגילים לראות בפונקציות הרגילות שלנו.