זרימת בקרה

try#

מריץ בלוק ומנתב כל חריגה שהוא זורק לבלוק catch, עם בלוק finally אופציונלי הרץ תמיד בדרך החוצה.

try/catch הוא תחביר טיפול חריגות מובנה של Perl. בלוק ה־try מבוצע; אם משפט כלשהו בו זורק (דרך die, חריגה לא־נתפסה מקריאה, או שגיאת זמן ריצה), הבקרה עוברת לבלוק ה־catch כשערך החריגה כבול ללקסיקלי בסוגריים. אם שום דבר אינו זורק, בלוק ה־catch מדולג. שלא כמו eval, return בתוך try מחזיר מהתת־שגרה העוטפת, לא מהבלוק - try הוא זרימת בקרה, לא מסגרת קריאה.

התחביר חייב להיות מופעל עם שומר תכונה:

use feature 'try';

תקציר#

try BLOCK catch (VAR) BLOCK
try BLOCK catch (VAR) BLOCK finally BLOCK

מה מקבלים בחזרה#

try הוא משפט, לא ביטוי, אבל הוא מניב את הערך המוערך האחרון של איזה הסתעפות שרצה - בלוק ה־try בהצלחה, או בלוק ה־catch בכשל. בשימוש כמשפט הסופי של תת־שגרה או בלוק do, אותו ערך הופך לתוצאה:

my $value = do {
    try {
        fetch_thing(@args);
    }
    catch ($e) {
        warn "fetch failed: $e";
        $DEFAULT;
    }
};

Do not write return inside a try that is being used this way

  • return exits the enclosing sub, bypassing the assignment.

הביטוי הסופי של בלוק finally מושלך; הוא אינו יכול לתרום לערך.

מצב גלובלי שהוא נוגע בו#

  • $@ אינו המנגנון כאן. try/catch כובל את החריגה ללקסיקלי המכונה ב־catch (VAR), לא ל־$@. קוד בתוך catch רואה את מה ש־$@ במקרה החזיק לפני ה־try; יש להסתמך על הלקסיקלי.

  • $_, $!, וגלובלים דינמיים אחרים אינם נגועים על ידי המבנה עצמו - רק על ידי הקוד שרץ בתוך הבלוקים.

המשתנה catch#

אחרי catch חייבת לבוא מיד הצהרת משתנה בסוגריים. my מובלע - יש לכתוב catch ($e), לא catch (my $e). המשתנה לקסיקלי לבלוק ה־catch ומחזיק את הערך הנזרק בדיוק כפי ש־die הסר אותו: מחרוזת, הפניה bless, או כל סקלר:

try {
    risky();
}
catch ($e) {
    if (ref $e && $e->isa('My::Exception')) {
        $e->rethrow if $e->is_fatal;
        warn $e->message;
    }
    else {
        warn "plain die: $e";
    }
}

אין ניתוב catch מרובה לפי מחלקת חריגה - יש לכתוב את הסיווג בעצמכם בתוך בלוק ה־catch היחיד.

finally#

בלוק שלישי אופציונלי רץ על כל מסלול יציאה ממבנה ה־try/catch: ירידה רגילה, חריגה שנתפסה על ידי catch, חריגה שנזרקה מחדש מ־catch, או העברה לא־מקומית (return, last, next, redo, goto) מתוך אחד הבלוקים.

try {
    open_resource();
    use_resource();
}
catch ($e) {
    warn "failed: $e";
}
finally {
    close_resource();
}

finally הוא המקום הנכון לקוד שחרור שחייב לרוץ ללא קשר לאיך הקטע המוגן יוצא - מטפלי קובץ, נעילות, שחזור מצב גלובלי.

לבלוק finally יש מגבלות שלשני הבלוקים האחרים אין:

  • אין return, אין goto, אין last / next / redo. ניסיון להפעיל אחד מאלה היא שגיאת זמן הידור או זמן ריצה.

  • ערך הביטוי הסופי מתעלמים ממנו - finally אינו יכול לתרום לערך המונב של המבנה.

finally נשאר ניסיוני ב־Perl 5.42 ומנפיק אזהרה בקטגוריית experimental::try בשימוש. try ו־catch עצמם הוסרו מסטטוס ניסיוני ב־5.40 ואינם מזהירים יותר.

ניגוד ל־eval { ... } / $@#

הדפוס הישן עדיין נתמך, אבל try/catch מתקן שלוש מהמורות ותיקות:

מהמורה

eval / $@

try / catch

דריסת $@ בין יציאת eval לבדיקת if ($@)

סכנה ממשית, דורש ריקוד local $@

חריגה כבולה ללקסיקלי רענן

return בתוך הבלוק המוגן

מחזיר מה־eval, לא מהתת־שגרה

מחזיר מהתת־שגרה העוטפת

הבחנה בין ”אין שגיאה“ ל“השגיאה הייתה שקר“

$@ יכול להיות falsey-but-set

catch רץ רק על זריקה אמיתית

יש להשתמש ב־try לקוד חדש. יש לפנות ל־eval EXPR רק כשבאמת צריך את ההידור בזמן ריצה של string-eval.

דוגמאות#

לכידת חריגה ורישומה ב־log:

use feature 'try';

try {
    my $x = call_a_function();
    $x < 100 or die "too big: $x";
    send_output($x);
}
catch ($e) {
    warn "unable to output a value: $e";
}
print "finished\n";

שימוש ב־try כביטוי בתוך do כדי לבחור fallback:

use feature 'try';

my $config = do {
    try { load_config($path) }
    catch ($e) { warn "using defaults: $e"; default_config() }
};

שחרור משאב ללא תנאי עם finally:

use feature 'try';
no warnings 'experimental::try';   # finally is still experimental

my $lock = acquire_lock();
try {
    do_critical_work();
}
catch ($e) {
    warn "critical work failed: $e";
}
finally {
    release_lock($lock);
}

סיווג חריגות בתוך בלוק catch יחיד:

use feature 'try';

try {
    fetch_remote();
}
catch ($e) {
    if (ref $e && $e->isa('Net::Timeout')) {
        retry_later();
    }
    elsif (ref $e && $e->isa('Net::Auth')) {
        die $e;                     # rethrow auth failures
    }
    else {
        warn "unexpected: $e";
    }
}

return בתוך try מחזיר מהתת־שגרה העוטפת - לא רק מהבלוק:

use feature 'try';

sub first_valid {
    for my $path (@_) {
        try {
            return load($path);     # exits first_valid on success
        }
        catch ($e) {
            warn "skip $path: $e";
        }
    }
    return;                         # all paths failed
}

מקרי קצה#

  • שומר תכונה נדרש. ללא use feature 'try' (או חבילת use v5.34 או מאוחרת יותר המאפשרת אותה), try הוא מזהה רגיל והמבנה הוא שגיאת תחביר.

  • catch הוא חובה. try { ... } לבדו אינו חוקי - לכל try נדרש catch. יש להשתמש ב־defer או finally אם רוצים ניקוי ללא טיפול חריגות, מזווג עם catch ($e) { die $e } טריוויאלי לזריקה מחדש אם גם רוצים לאפשר לחריגה להתפשט.

  • זריקה מחדש. בתוך catch, die $e זורק מחדש. אין זריקה מחדש מובלעת אם פשוט נופלים מסוף ה־catch; עשייה כך בולעת את החריגה. יש להחליט בכוונה.

  • $@ אינו מנוקה על ידי כניסה ל־try ואינו מוגדר על ידי חריגה שנתפסה. אסור לקרוא את $@ בתוך catch בציפייה לערך הנזרק - יש לקרוא את הלקסיקלי.

  • caller() אינו רואה את try. כמו while או foreach, try אינו מסגרת קריאה. caller מדלג עליו, וזה נכון: מכיוון ש־return מתפשט דרך try, ל־try אין סמנטיקת החזרה עצמאית שכדאי לחשוף לאינטרוספקציה.

  • בקרות לולאה עוברות דרכם. last, next, ו־redo בתוך try או catch פועלים על הלולאה העוטפת, לא על מבנה ה־try. בלוק finally עדיין ירוץ לפני שההעברה מסתיימת.

  • מגבלות finally נאכפות. return, goto, ובקרות לולאה בתוך finally הן שגיאות. אם צריך ניקוי מותנה שעלול לבטל, יש לעשות זאת ב־catch או מחוץ למבנה.

  • אין סוכר ביטוי־try מעבר להנבת משפט אחרון. try אינו ביטוי כללי; הוא יכול לייצר ערך רק כשהוא המשפט הסופי של בלוק המניב (do, תת־שגרה). my $x = try { ... } catch ($e) { ... }; היא שגיאת תחביר.

הבדלים מהמקור#

תואם במלואו ל־Perl 5.42 במקור.

  • try ו־catch אינם ניסיוניים (החל מ־5.40) ואינם מנפיקים אזהרות.

  • finally עדיין ניסיוני ב־5.42 ומנפיק אזהרות experimental::try בשימוש; יש להשתיק עם no warnings 'experimental::try'.

ראו גם#

  • catch - הבלוק השני שהוא חובה; מקבל את החריגה בלקסיקלי בסוגריים

  • finally - בלוק שלישי אופציונלי לניקוי ללא תנאי; עדיין ניסיוני ב־5.42

  • die - צד הזריקה של המבנה; כל סקלר (מחרוזת, הפניה bless, עצם) יכול להיות הערך הנזרק

  • eval - הדפוס הישן של block-eval / $@, עדיין נדרש עבור eval של מחרוזת; עדיף try לטיפול חריגות טהור

  • defer - ניקוי ללא תנאי ללא try/catch סובב; הבסיס הסמנטי שעליו finally מוגדר

  • return - בתוך try מחזיר מהתת־שגרה העוטפת, לא מהבלוק; בתוך finally זוהי שגיאה