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
This module provides a syntax plugin that implements a block which executes when the containing scope has finished.
It similar to features provided by other languages; Swift, Zig, Jai, Nim and Odin all provide this. Note that while Go also provides a defer
keyword, the semantics here are not the same. Go's version defers until the end of the entire function, rather than the closest enclosing scope as is common to most other languages, and this module.
The operation can be considered a little similar to an END
block, but with the following key differences:
A
defer
block runs at the time that execution leaves the block it is declared inside, whereas anEND
block runs at the end time of the entire program regardless of its location.A
defer
block is invoked at the time its containing scope has finished, which means it might run again if the block is entered again later in the program. AnEND
block will only ever run once.A
defer
block will only take effect if execution reaches the line it is declared on; if the line is not reached then nothing happens. AnEND
block will always be invoked once declared, regardless of the dynamic extent of execution at runtime.
defer
blocks are primarily intended for cases such as resource finalisation tasks that may be conditionally required.
For example in the synopsis code, after normal execution the statement handle will be finished using the $sth->finish
method, then the database will be disconnected with $dbh->disconnect
. If instead the prepare method failed then the database will still be disconnected, but there is no need to finish with the statement handle as the second defer
block was never encountered.
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 is pushed to a queue of pending operations, which is then flushed when the surrounding block finishes for any reason - either by implicit fallthrough, or explicit termination by 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 appear within the same block, they are pushed to the queue in LIFO order; the last one encountered is the first one to be executed.
{
defer { say "This happens second"; }
defer { say "This happens first"; }
}
A defer
block will only take effect if the statement itself is actually encountered during normal execution. This is in direct contrast to an END
phaser which always occurs. This makes it ideal for handling finalisation of a resource which 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 does not need to run.
my $resource = Resource->open( ... );
defer { $resource->close; }
Unlike as would happen with e.g. 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, if a defer
block is being run because of exceptional return of its scope, any further exceptions it attempts to raise are turned into warnings. This ensures that the original exception which caused the stack-unwind to run the block in the first place does not get overwritten on the way.
Because a defer
block is a true block (e.g. in 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. This is 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.
TODO
This module contains a unit test file copied and edited from my core perl branch to provide the same syntax. Several test cases are currently commented out because this implementation does not yet handle them:
Detection logic of defer-during-throw is currently based on the truth of the
ERRSV
($@
), which means it is 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 thedefer
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>