package Try::Tiny;
use 5.006;
-# ABSTRACT: minimal try/catch with proper preservation of $@
+# ABSTRACT: Minimal try/catch with proper preservation of $@
+
+our $VERSION = '0.29';
use strict;
use warnings;
-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} }
+BEGIN {
+ my $su = $INC{'Sub/Util.pm'} && defined &Sub::Util::set_subname;
+ my $sn = $INC{'Sub/Name.pm'} && eval { Sub::Name->VERSION(0.08) };
+ unless ($su || $sn) {
+ $su = eval { require Sub::Util; } && defined &Sub::Util::set_subname;
+ unless ($su) {
+ $sn = eval { require Sub::Name; Sub::Name->VERSION(0.08) };
+ }
+ }
+
+ *_subname = $su ? \&Sub::Util::set_subname
+ : $sn ? \&Sub::Name::subname
+ : sub { $_[1] };
+ *_HAS_SUBNAME = ($su || $sn) ? sub(){1} : sub(){0};
+}
+
+my %_finally_guards;
# 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
# $catch->();
# 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;
+ _subname(caller().'::try {...} ' => $try)
+ if _HAS_SUBNAME;
+
+ # set up scope guards to invoke the finally blocks at the end.
+ # this should really be a function scope lexical variable instead of
+ # file scope + local but that causes issues with perls < 5.20 due to
+ # perl rt#119311
+ local $_finally_guards{guards} = [
+ map { Try::Tiny::ScopeGuard->_new($_) }
+ @finally
+ ];
# save the value of $@ so we can set $@ back to it in the beginning of the eval
# and restore $@ after the eval finishes
$try->();
};
- return 1; # properly set $fail to false
+ 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 =
- map { Try::Tiny::ScopeGuard->_new($_, $failed ? $error : ()) }
- @finally;
-
# at this point $failed contains a true value if the eval died, even if some
# destructor overwrote $@ as the eval was unwinding.
if ( $failed ) {
+ # pass $error to the finally blocks
+ push @$_, $error for @{$_finally_guards{guards}};
+
# if we got an error, invoke the catch block.
if ( $catch ) {
# This works like given($error), but is backwards compatible and
croak 'Useless bare catch()' unless wantarray;
+ _subname(caller().'::catch {...} ' => $block)
+ if _HAS_SUBNAME;
return (
bless(\$block, 'Try::Tiny::Catch'),
@rest,
croak 'Useless bare finally()' unless wantarray;
+ _subname(caller().'::finally {...} ' => $block)
+ if _HAS_SUBNAME;
return (
bless(\$block, 'Try::Tiny::Finally'),
@rest,
package # hide from PAUSE
Try::Tiny::ScopeGuard;
- use constant UNSTABLE_DOLLARAT => ($] < '5.013002') ? 1 : 0;
+ use constant UNSTABLE_DOLLARAT => ("$]" < '5.013002') ? 1 : 0;
sub _new {
shift;
assign C<"bar"> to C<$x>:
my $x = try { die "foo" } catch { "bar" };
- my $x = try { die "foo" } || { "bar" };
- my $x = (try { die "foo" }) // { "bar" };
+ my $x = try { die "foo" } || "bar";
+ my $x = (try { die "foo" }) // "bar";
my $x = eval { die "foo" } || "bar";
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>.
C<$@> must be properly localized before invoking C<eval> in order to avoid this
issue.
-More specifically, C<$@> is clobbered at the beginning of the C<eval>, which
-also makes it impossible to capture the previous error before you die (for
+More specifically,
+L<before Perl version 5.14.0|perl5140delta/"Exception Handling">
+C<$@> was clobbered at the beginning of the C<eval>, which
+also made 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 (the one
C<$@> could also be an overloaded error object that evaluates to false, but
that's asking for trouble anyway.
-The classic failure mode is:
+The classic failure mode (fixed in L<Perl 5.14.0|perl5140delta/"Exception Handling">) is:
sub Object::DESTROY {
eval { ... }
This is because an C<eval> that caught a C<die> will always return a false
value.
-=head1 SHINY SYNTAX
+=head1 ALTERNATE SYNTAX
-Using Perl 5.10 you can use L<perlsyn/"Switch statements">.
+Using Perl 5.10 you can use L<perlsyn/"Switch statements"> (but please don't,
+because that syntax has since been deprecated because there was too much
+unexpected magical behaviour).
+
+=for stopwords topicalizer
The C<catch> block is invoked in a topicalizer context (like a C<given> block),
but note that you can't return a useful value from C<catch> using the C<when>
=item *
C<@_> is not available within the C<try> block, so you need to copy your
-arglist. In case you want to work with argument values directly via C<@_>
+argument list. In case you want to work with argument values directly via C<@_>
aliasing (i.e. allow C<$_[1] = "foo">), you need to pass C<@_> by reference:
sub foo {
say "This text WILL NEVER appear!";
}
-
- # or
-
+ # OR
sub parent_sub_with_catch {
my $success = try {
die;
# do something with $_
return undef; #see note
};
+ return unless $success;
+
+ say "This text WILL NEVER appear!";
}
Note that if you have a C<catch> block, it must return C<undef> for this to work,
will not report this when using full stack traces, though, because
C<%Carp::Internal> is used. This lack of magic is considered a feature.
+=for stopwords unhygienically
+
=item *
The value of C<$_> in the C<catch> block is not guaranteed to be the value of
the exception thrown (C<$@>) in the C<try> block. There is no safe way to
-ensure this, since C<eval> may be used unhygenically in destructors. The only
+ensure this, since C<eval> may be used unhygienically in destructors. The only
guarantee is that the C<catch> will be called if an exception is thrown.
=item *
}
}
-Note that this behavior was changed once again in L<Perl5 version 18
-|https://metacpan.org/module/perldelta#given-now-aliases-the-global-_>.
-However, since the entirety of lexical C<$_> is now L<considired experimental
+Note that this behavior was changed once again in
+L<Perl5 version 18|https://metacpan.org/module/perldelta#given-now-aliases-the-global-_>.
+However, since the entirety of lexical C<$_> is now L<considered experimental
|https://metacpan.org/module/perldelta#Lexical-_-is-now-experimental>, it
is unclear whether the new version 18 behavior is final.
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/doy/try-tiny/>
-
=cut
-