זרימת בקרה

defer#

לתזמן בלוק שירוץ כאשר התחום העוטף יוצא, מכל סיבה שהיא.

defer היא מילת מפתח לבקרת זרימה, לא פונקציה. כתיבת defer { ... } בתוך תחום רושמת את הבלוק לביצוע מאוחר יותר; כאשר הבקרה עוזבת את הבלוק העוטף - על־ידי נפילה מסוף הבלוק, על־ידי return, על־ידי die, על־ידי goto, או על־ידי הוראת בקרת לולאה (next, last, redo) - הבלוק המאוחסן רץ בדרך החוצה. זהו המבנה הסטנדרטי לשיוך רכישה עם שחרור (open/close, lock/unlock, push/pop) מבלי להסתמך על הורסי עצמים או לעטוף כל מסלול ב־eval.

התכונה היא ניסיונית וחייבת להיות מופעלת. יש להצטרף במפורש או לאמץ את החבילה:

use feature 'defer';    # explicit
use v5.36;              # bundle - enables defer among others

תחת use warnings 'experimental::defer' (או use warnings פשוט) השימוש הראשון פולט אזהרה ניסיונית. יש להשתיק אותה עם no warnings 'experimental::defer' ברגע שהוחלט להסתמך על התכונה.

תקציר#

defer BLOCK

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

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

defer היא הוראה, לא ביטוי. היא לא תורמת ערך להקשר שמסביבה. ערך ההחזרה של הבלוק עצמו מושלך כאשר הוא רץ ביציאת התחום.

מתי הבלוק הדחוי רץ#

הבלוק הדחוי מופעל פעם אחת, כאשר הבקרה עוזבת את הבלוק העוטף ישירות, ללא תלות באופן בו הבקרה עוזבת:

  • נפילה רגילה מסוף הבלוק,

  • return מפורש מתת־השגרה המכילה,

  • חריגה שנזרקה על־ידי die או הופצה מתת־שגרה שנקראה,

  • goto מחוץ לבלוק,

  • בקרת לולאה דרך next, last, או redo.

אם זרימת הביצוע לעולם לא מגיעה אל הוראת defer עצמה, הבלוק אינו נכנס לתור ולא רץ. זהו ההיפך הישיר מ־END phaser, שהמהדר מכניס לתור ללא תנאי:

use feature 'defer';

{
    defer { say "This will run"; }
    return;
    defer { say "This will not"; }   # never reached, never scheduled
}

סדר LIFO#

מספר בלוקי defer באותו תחום רצים בסדר last-in, first-out - הבלוק שתוזמן לאחרונה רץ ראשון. זה תואם את הקינון הטבעי של רכישת משאבים: הדבר האחרון שנרכש הוא הדבר הראשון שמשוחרר.

use feature 'defer';

{
    defer { say "1"; }
    defer { say "2"; }
    defer { say "3"; }
}
# prints 3, then 2, then 1

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

אין ישירות. הבלוק הדחוי רואה גלובליים של המפרש ($_, $!, $@, מטפל הקובץ הנבחר, וכו«) עם הערכים שהם מחזיקים ברגע שהבלוק רץ, ולא ברגע שהוא תוזמן. defer שרוצה לשמר את $! או $@ לרוחב הניקוי חייב לשמור ולשחזר אותם בעצמו - אחרת קריאת מערכת בתוך הבלוק תדרוס אותם והקורא לתת־השגרה העוטפת יראה את הערכים שנדרסו.

defer {
    local ($!, $@);
    close $fh;           # clobbers $! on failure - local'd copy absorbs it
}

דוגמאות#

לשייך open עם ה־close שלו, מובטח לרוץ גם ב־die:

use feature 'defer';

sub read_config {
    open my $fh, "<", "config.ini" or die "open: $!";
    defer { close $fh; }

    while (<$fh>) { ... }
    # close $fh runs here on fallthrough, on return, and on die.
}

לשחרר נעילה של משאב משותף בכל מסלול יציאה:

use feature 'defer';

sub critical_section {
    $lock->acquire;
    defer { $lock->release; }

    do_work();            # release runs on fallthrough
    return if $shortcut;  # release runs before the return completes
    die "boom" if $bad;   # release runs, then the exception propagates
}

ניקוי LIFO של משאבים מקוננים - המשאב החיצוני משוחרר אחרון, מה שלרוב מבוקש:

use feature 'defer';

sub copy_file {
    open my $in,  "<", $src or die "open $src: $!";
    defer { close $in; }

    open my $out, ">", $dst or die "open $dst: $!";
    defer { close $out; }

    print {$out} $_ while <$in>;
    # close $out runs first, then close $in.
}

להתאים זמנית את מצב המפרש ולשחזרו ביציאה מבלי להשתמש ב־local:

use feature 'defer';

sub with_separator {
    my $saved = $,;
    $, = " | ";
    defer { $, = $saved; }

    print @_, "\n";
}

מקרי קצה#

  • תזמון מותנה. הוראת defer מתזמנת את הבלוק שלה רק כאשר הבקרה אכן מגיעה אליה. return מוקדם או die שקורה לפני שורת ה־defer משאיר כלום לרוץ. יש למקם את ה־defer מיד לאחר הרכישה המשויכת כך שלא יוכלו להיפרד על־ידי כישלון ביניים.

  • ברגע שמתוזמן, רץ תמיד. אין מנגנון לבטל את תזמון בלוק defer. אם הרכישה שהבלוק משחרר נכשלה, יש או לא לתזמן את ה־defer (לשמור אותו לאחר בדיקת ההצלחה) או להפוך את הבלוק לאידמפוטנטי כך ששחרור מיותר יהיה לא־מזיק.

  • אין בריחת בקרת זרימה מהבלוק. בלוק defer רשאי לזרוק חריגה, אך הוא אינו רשאי לבצע return מתת־השגרה העוטפת, goto לתווית מחוץ לעצמו, או להריץ next, last, או redo עבור לולאה שעוטפת את ה־defer. מבנים אלה תקינים בתוך ה־defer כאשר הם פועלים על לולאות מוכלות לחלוטין בתוך הגוף של הבלוק עצמו:

    defer {
        foreach (1 .. 5) { last if $_ == 3; }   # permitted
    }
    
    foreach (6 .. 10) {
        defer { last if $_ == 8; }              # forbidden
    }
    
  • חריגה במהלך פריקת חריגה. אם בלוק defer רץ משום שחריגה מתפשטת והבלוק עצמו זורק, השפה אינה מציינת את החריגה המתקבלת - רק שהקורא יראה אחת. אין להסתמך על איזו חריגה תנצח. יש לעטוף ניקוי מסוכן ב־eval אם נדרש לשמר את שני האותות.

  • חריגות מתפשטות באופן רגיל. חריגה שנזרקת על־ידי בלוק defer שרץ מסיבה אחרת (יציאה רגילה, return, בקרת לולאה) מתפשטת לקורא בדיוק כמו כל חריגה אחרת. close שנכשל בניקוי עלול לכן להפוך פונקציה שנראית מצליחה ל־die שהקורא רואה.

  • $@ ו־$! בתוך הבלוק. במסלול היציאה המונע על־ידי חריגה, $@ מחזיק את החריגה שבטיסה בזמן שבלוק ה־defer רץ. קריאה ממנו תקינה; החלפתו משנה מה הקורא יראה לאחר שהפריקה תסתיים.

  • גרעיניות תחום. defer נקשר לבלוק העוטף ישירות, לא לתת־השגרה העוטפת. defer בתוך if (...) { defer { ... } } רץ כאשר בלוק ה־if יוצא, לא כאשר תת־השגרה חוזרת. יש למקם את ה־defer בתחום תת־השגרה אם רוצים סמנטיקת יציאה־מתת־שגרה.

  • אזהרה ניסיונית. תחת use warnings המופע הראשון מתריע. יש להצטרף במכוון עם no warnings 'experimental::defer' ברגע שהתחייבת לתכונה.

  • לא זמין ללא שומר התכונה. ללא use feature 'defer' (או חבילה שמפעילה אותה), defer { ... } מנותח כקריאה לתת־שגרה בשם defer שאחריה בלוק - כמעט בוודאות לא מה שהתכוונת אליו, ובדרך כלל שגיאת זמן־הידור.

הבדלים מ־upstream#

תואם מלא ל־upstream Perl 5.42. התכונה נשארת מסומנת ניסיונית ב־pperl כדי לשקף את הסיווג של upstream; התנהגות (תזמון בביצוע הוראה, סדר LIFO, התפשטות חריגות, הגבלות על בריחת בקרת זרימה) תואמת ל־Perl 5.42.

ראו גם#

  • eval - הכלי המבוסס להפיכת חריגות לערכי החזרה; defer משלים אותו על־ידי הבטחת ניקוי ללא עטיפת eval מפורשת בכל מסלול

  • die - אחד ממסלולי היציאה שמפעילים בלוקים דחויים; יש לקרוא לצד defer בעיצוב ניקוי

  • return - מסלול יציאה נוסף; defer שתוזמן לפני return רץ לפני שהקורא מתחדש

  • local - המבנה האחר לשחזור מצב מוגבל־תחום; יש לבחור ב־local לתיחום דינמי של משתנה יחיד, ב־defer לפעולות ניקוי שרירותיות

  • $@ - החריגה הנוכחית; נראית בתוך בלוק defer שרץ במסלול פריקת החריגה

  • $! - errno של המערכת; נדרס בקלות על־ידי קריאות מערכת בניקוי, יש להשתמש ב־local בתוך הבלוק הדחוי אם הקורא צריך לשמרו