Syntax...:Defer(3pm)

πŸ“¦
version 0.11 Β· 3 versions
, in fedora 43

✨ Syntax::Keyword::Defer ✨

okay bestie, settle in β€” this is a page from the User Contributed Perl Documentation manual, and it's about Syntax::Keyword::Defer, a module that lets you run code when you leave a block. iconic behavior honestly πŸ’…


πŸŽ€ NAME πŸŽ€

"Syntax::Keyword::Defer" β€” execute code when leaving a block πŸ’–


πŸ¦‹ SYNOPSIS πŸ¦‹

use Syntax::Keyword::Defer;

{
   my $dbh = DBI->connect( ... ) or die "Cannot connect";
   defer { $dbh->disconnect; }

   my $sth = $dbh->prepare( ... ) or die "Cannot prepare";
   defer { $sth->finish; }

   ...
}

🌸 DESCRIPTION 🌸

So basically this module ships a syntax plugin that gives you a block which runs when the containing scope is done. Like, scope's over, block goes off. periodt.

It's giving the same energy as features other languages have β€” Swift, Zig, Jai, Nim and Odin all provide this. πŸ’« Now, quick note bestie: Go also has a "defer" keyword, but the semantics here are not the same. Go's version waits until the end of the entire function, not the closest enclosing scope β€” and the closest enclosing scope is what's common to most other languages, and what this module does. iykyk 🌈

You can lowkey think of it as a little like an "END" block, except here's where they diverge:

  • A "defer" block runs at the moment execution leaves the block it was declared inside, whereas an "END" block runs at the end time of the entire program, no matter where it's sitting.
  • A "defer" block fires when its containing scope finishes β€” which means it might run again if that block gets entered again later in the program. An "END" block will only ever run once. no reruns, no encores.
  • A "defer" block only takes effect if execution actually reaches the line it's declared on; if that line never gets reached, nothing happens at all. An "END" block will always be invoked once declared, regardless of the dynamic extent of execution at runtime.

"defer" blocks are mainly intended for stuff like resource finalisation tasks that may only be conditionally required. πŸ’–

For example, in the synopsis code: after normal execution the statement handle gets finished using the "$sth->finish" method, and then the database gets disconnected with "$dbh->disconnect". But if the prepare method failed instead, the database still gets disconnected β€” but there's no need to finish the statement handle, because that second "defer" block was never encountered. slay, conditional cleanup 🫢


πŸ‘‘ KEYWORDS πŸ‘‘

πŸ’… defer

defer {
   STATEMENTS...
}

The "defer" keyword introduces a block which runs its code body at the time that its immediately surrounding code block finishes. πŸŽ€

When the "defer" statement is encountered, the body of the code block gets pushed onto a queue of pending operations, and that queue gets flushed when the surrounding block finishes β€” for any reason. Could be implicit fallthrough, could be explicit termination via "return", "die", or any of the loop control statements "next", "last" or "redo". πŸŒ€

sub f
{
   defer { say "The function has now returned"; }
   return 123;
}

If multiple "defer" statements show up in the same block, they get pushed onto the queue in LIFO order β€” the last one encountered is the first one to be executed. it's giving stack energy fr fr πŸ’€

{
   defer { say "This happens second"; }
   defer { say "This happens first"; }
}

A "defer" block only takes effect if the statement itself is actually encountered during normal execution. This is in direct contrast to an "END" phaser, which always occurs. Which makes it ideal for handling finalisation of a resource that was created on a nearby previous line, where the code to create it might have thrown an exception instead. Because the exception skipped over the "defer" statement, the code body doesn't need to run. πŸ’–

my $resource = Resource->open( ... );
defer { $resource->close; }

Unlike what would happen with, say, a "DESTROY" method on a guard object, any exceptions thrown from a "defer" block are still propagated up to the caller in the usual way. 😀

use Syntax::Keyword::Defer;

sub f
{
   my $count = 0;
   defer { $count or die "Failed to increment count"; }

   # some code here
}

f();

$ perl example.pl
Failed to increment count at examples.pl line 6.

However β€” and this is important bestie, don't skip it β€” if a "defer" block is being run because of an exceptional return of its scope, any further exceptions it tries to raise get turned into warnings. This makes sure the original exception that caused the stack-unwind to run the block in the first place doesn't get overwritten on the way out. protecting the original drama πŸ’…

And because a "defer" block is a true block (like, the same way something like an "if () {...}" block is), rather than an anonymous sub, it does not appear to caller() or other stack-inspection tricks. Which is super useful for calling croak(), for example. πŸ”₯

sub g
{
   my $count = 0;
   defer { $count or croak "Expected some items"; }

   $count++ for @_;
}

Here, croak() will correctly report the caller of the g() function, rather than appearing to be called from an "__ANON__" sub invoked at the end of the function itself. no cap. ✨


🌈 TODO 🌈

This module contains a unit test file that got copied and edited from the author's core perl branch, to provide the same syntax. Several test cases are currently commented out because this implementation doesn't handle them yet: 😭

  • Detection logic of defer-during-throw is currently based on the truth of the "ERRSV" ($@), which means it's liable to false positives. There may not be much that can be done about this.
  • Try to fix the double-exception test failure on Perl versions before v5.20. (Test currently skipped on those versions.)
  • Try to detect and forbid nonlocal flow control ("goto", "next/last/redo") from leaving the "defer" block.

E.g. currently the following will crash the interpreter:

sub func { last ITEM }

ITEM: foreach(1..10) {
   say;
   defer { func() }
}

πŸ’œ AUTHOR πŸ’œ

Paul Evans <leonerd@leonerd.org.uk> 🫢


okay that's the whole page bestie, you're officially fluent in defer now, go be the main character of your own scope πŸ’…βœ¨

this lil tour was brought to you by perl v5.42.0, dated 2025-07-25, from the Syntax...:Defer(3pm) page β€” mwah, take care of your resources πŸŽ€πŸ¦‹

πŸ“¦ where this page comes from