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מחוץ לבלוק,
אם זרימת הביצוע לעולם לא מגיעה אל הוראת 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בתוך הבלוק הדחוי אם הקורא צריך לשמרו