package Try::Tiny;
+use 5.006;
+# ABSTRACT: minimal try/catch with proper preservation of $@
use strict;
use warnings;
-our $VERSION = "0.12";
-$VERSION = eval $VERSION if $VERSION =~ /_/;
-
-use base 'Exporter';
+use Exporter 5.57 'import';
our @EXPORT = our @EXPORT_OK = qw(try catch finally);
use Carp;
$Carp::Internal{+__PACKAGE__}++;
+BEGIN { eval "use Sub::Name; 1" or *{subname} = sub {1} }
+
# Need to prototype as @ not $$ because of the way Perl evaluates the prototype.
# Keeping it at $$ means you only ever get 1 sub because we need to eval in a list
# context & not a scalar one
# to $failed
my $wantarray = wantarray;
- my ( $catch, @finally );
+ # work around perl bug by explicitly initializing these, due to the likelyhood
+ # this will be used in global destruction (perl rt#119311)
+ my ( $catch, @finally ) = ();
# find labeled blocks in the argument list.
# catch and finally tag the blocks by blessing a scalar reference to them.
foreach my $code_ref (@code_refs) {
- next unless $code_ref;
-
- my $ref = ref($code_ref);
- if ( $ref eq 'Try::Tiny::Catch' ) {
+ if ( ref($code_ref) eq 'Try::Tiny::Catch' ) {
+ croak 'A try() may not be followed by multiple catch() blocks'
+ if $catch;
$catch = ${$code_ref};
- } elsif ( $ref eq 'Try::Tiny::Finally' ) {
+ } elsif ( ref($code_ref) eq 'Try::Tiny::Finally' ) {
push @finally, ${$code_ref};
} else {
- confess("Unknown code ref type given '${ref}'. Check your usage & try again");
+ croak(
+ 'try() encountered an unexpected argument ('
+ . ( defined $code_ref ? $code_ref : 'undef' )
+ . ') - perhaps a missing semi-colon before or'
+ );
}
}
- # save the value of $@ so we can set $@ back to it in the beginning of the eval
- my $prev_error = $@;
-
- my ( @ret, $error, $failed );
-
# FIXME consider using local $SIG{__DIE__} to accumulate all errors. It's
# not perfect, but we could provide a list of additional errors for
# $catch->();
- {
- # localize $@ to prevent clobbering of previous value by a successful
- # eval.
- local $@;
+ # name the blocks if we have Sub::Name installed
+ my $caller = caller;
+ subname("${caller}::try {...} " => $try);
+ subname("${caller}::catch {...} " => $catch) if $catch;
+ subname("${caller}::finally {...} " => $_) foreach @finally;
+
+ # save the value of $@ so we can set $@ back to it in the beginning of the eval
+ # and restore $@ after the eval finishes
+ my $prev_error = $@;
- # failed will be true if the eval dies, because 1 will not be returned
- # from the eval body
- $failed = not eval {
- $@ = $prev_error;
-
- # evaluate the try block in the correct context
- if ( $wantarray ) {
- @ret = $try->();
- } elsif ( defined $wantarray ) {
- $ret[0] = $try->();
- } else {
- $try->();
- };
-
- return 1; # properly set $fail to false
+ my ( @ret, $error );
+
+ # failed will be true if the eval dies, because 1 will not be returned
+ # from the eval body
+ my $failed = not eval {
+ $@ = $prev_error;
+
+ # evaluate the try block in the correct context
+ if ( $wantarray ) {
+ @ret = $try->();
+ } elsif ( defined $wantarray ) {
+ $ret[0] = $try->();
+ } else {
+ $try->();
};
- # copy $@ to $error; when we leave this scope, local $@ will revert $@
- # back to its previous value
- $error = $@;
- }
+ return 1; # properly set $failed to false
+ };
+
+ # preserve the current error and reset the original value of $@
+ $error = $@;
+ $@ = $prev_error;
# set up a scope guard to invoke the finally block at the end
my @guards =
sub catch (&;@) {
my ( $block, @rest ) = @_;
+ croak 'Useless bare catch()' unless wantarray;
+
return (
bless(\$block, 'Try::Tiny::Catch'),
@rest,
sub finally (&;@) {
my ( $block, @rest ) = @_;
+ croak 'Useless bare finally()' unless wantarray;
+
return (
bless(\$block, 'Try::Tiny::Finally'),
@rest,
package # hide from PAUSE
Try::Tiny::ScopeGuard;
+ use constant UNSTABLE_DOLLARAT => ($] < '5.013002') ? 1 : 0;
+
sub _new {
shift;
bless [ @_ ];
}
sub DESTROY {
- my @guts = @{ shift() };
- my $code = shift @guts;
- $code->(@guts);
+ my ($code, @args) = @{ $_[0] };
+
+ local $@ if UNSTABLE_DOLLARAT;
+ eval {
+ $code->(@args);
+ 1;
+ } or do {
+ warn
+ "Execution of finally() block $code resulted in an exception, which "
+ . '*CAN NOT BE PROPAGATED* due to fundamental limitations of Perl. '
+ . 'Your program will continue as if this event never took place. '
+ . "Original exception text follows:\n\n"
+ . (defined $@ ? $@ : '$@ left undefined...')
+ . "\n"
+ ;
+ }
}
}
=pod
-=head1 NAME
-
-Try::Tiny - minimal try/catch with proper localization of $@
-
=head1 SYNOPSIS
You can use Try::Tiny's C<try> and C<catch> to expect and handle exceptional
which cannot be handled using local. You can add as many C<finally> blocks to a
given C<try> block as you like.
+Note that adding a C<finally> block without a preceding C<catch> block
+suppresses any errors. This behaviour is consistent with using a standalone
+C<eval>, but it is not consistent with C<try>/C<finally> patterns found in
+other programming languages, such as Java, Python, Javascript or C#. If you
+learnt the C<try>/C<finally> pattern from one of these languages, watch out for
+this.
+
=head1 EXPORTS
All functions are exported by default using L<Exporter>.
Once all execution is finished then the C<finally> block, if given, will execute.
-=item catch (&;$)
+=item catch (&;@)
Intended to be used in the second argument position of C<try>.
local $@ = $_;
-=item finally (&;$)
+=item finally (&;@)
try { ... }
catch { ... }
not do anything about handling possible errors coming from code located in these
blocks.
+Furthermore B<exceptions in C<finally> blocks are not trappable and are unable
+to influence the execution of your program>. This is due to limitation of
+C<DESTROY>-based scope guards, which C<finally> is implemented on top of. This
+may change in a future version of Try::Tiny.
+
In the same way C<catch()> blesses the code reference this subroutine does the same
except it bless them as C<Try::Tiny::Finally>.
also makes it impossible to capture the previous error before you die (for
instance when making exception objects with error stacks).
-For this reason C<try> will actually set C<$@> to its previous value (before
-the localization) in the beginning of the C<eval> block.
+For this reason C<try> will actually set C<$@> to its previous value (the one
+available before entering the C<try> block) in the beginning of the C<eval>
+block.
=head2 Localizing $@ silently masks errors
my $success = try {
die;
1;
+ };
+ return unless $success;
+
+ say "This text WILL NEVER appear!";
+ }
+ # OR
+ sub parent_sub_with_catch {
+ my $success = try {
+ die;
+ 1;
}
+ catch {
+ # do something with $_
+ return undef; #see note
+ };
return unless $success;
say "This text WILL NEVER appear!";
I gave a lightning talk about this module, you can see the slides (Firefox
only):
-L<http://nothingmuch.woobling.org/talks/takahashi.xul?data=yapc_asia_2009/try_tiny.txt>
+L<http://web.archive.org/web/20100628040134/http://nothingmuch.woobling.org/talks/takahashi.xul>
Or read the source:
-L<http://nothingmuch.woobling.org/talks/yapc_asia_2009/try_tiny.yml>
+L<http://web.archive.org/web/20100305133605/http://nothingmuch.woobling.org/talks/yapc_asia_2009/try_tiny.yml>
=head1 VERSION CONTROL
-L<http://github.com/nothingmuch/try-tiny/>
-
-=head1 AUTHOR
-
-Yuval Kogman E<lt>nothingmuch@woobling.orgE<gt>
-
-=head1 COPYRIGHT
-
- Copyright (c) 2009 Yuval Kogman. All rights reserved.
- This program is free software; you can redistribute
- it and/or modify it under the terms of the MIT license.
+L<http://github.com/doy/try-tiny/>
=cut