X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FTry%2FTiny.pm;h=91307b465bd7105d4fae21814641b9df89224293;hb=79039ae433128d824ddf3d930909434f9fe645d3;hp=9d46a7629ef1eb40ac5ad59c085d89d73db1166f;hpb=2b0d579d80e5316f00183930df181ea36f80e517;p=p5sagit%2FTry-Tiny.git diff --git a/lib/Try/Tiny.pm b/lib/Try/Tiny.pm index 9d46a76..91307b4 100644 --- a/lib/Try/Tiny.pm +++ b/lib/Try/Tiny.pm @@ -1,17 +1,18 @@ 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 @@ -23,21 +24,26 @@ sub try (&;@) { # 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' + ); } } @@ -45,6 +51,12 @@ sub try (&;@) { # not perfect, but we could provide a list of additional errors for # $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; + # 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 = $@; @@ -66,9 +78,10 @@ sub try (&;@) { }; return 1; # properly set $fail to false - } and $error = $@; + }; - # reset the original value of $@ + # 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 @@ -101,6 +114,8 @@ sub try (&;@) { sub catch (&;@) { my ( $block, @rest ) = @_; + croak 'Useless bare catch()' unless wantarray; + return ( bless(\$block, 'Try::Tiny::Catch'), @rest, @@ -110,6 +125,8 @@ sub catch (&;@) { sub finally (&;@) { my ( $block, @rest ) = @_; + croak 'Useless bare finally()' unless wantarray; + return ( bless(\$block, 'Try::Tiny::Finally'), @rest, @@ -120,15 +137,30 @@ sub finally (&;@) { 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" + ; + } } } @@ -138,10 +170,6 @@ __END__ =pod -=head1 NAME - -Try::Tiny - minimal try/catch with proper preservation of $@ - =head1 SYNOPSIS You can use Try::Tiny's C and C to expect and handle exceptional @@ -204,6 +232,13 @@ C blocks are always executed making them suitable for cleanup code which cannot be handled using local. You can add as many C blocks to a given C block as you like. +Note that adding a C block without a preceding C block +suppresses any errors. This behaviour is consistent with using a standalone +C, but it is not consistent with C/C patterns found in +other programming languages, such as Java, Python, Javascript or C#. If you +learnt the C/C pattern from one of these languages, watch out for +this. + =head1 EXPORTS All functions are exported by default using L. @@ -235,7 +270,7 @@ still be invoked. Once all execution is finished then the C block, if given, will execute. -=item catch (&;$) +=item catch (&;@) Intended to be used in the second argument position of C. @@ -255,7 +290,7 @@ L), you'll need to do: local $@ = $_; -=item finally (&;$) +=item finally (&;@) try { ... } catch { ... } @@ -299,6 +334,11 @@ B block>. C blocks are not trappable and are unable +to influence the execution of your program>. This is due to limitation of +C-based scope guards, which C is implemented on top of. This +may change in a future version of Try::Tiny. + In the same way C blesses the code reference this subroutine does the same except it bless them as C. @@ -462,7 +502,21 @@ Instead, you should capture the return value: 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!"; @@ -574,25 +628,15 @@ issues with C<$@>, but you still need to localize to prevent clobbering. I gave a lightning talk about this module, you can see the slides (Firefox only): -L +L Or read the source: -L +L =head1 VERSION CONTROL -L - -=head1 AUTHOR - -Yuval Kogman Enothingmuch@woobling.orgE - -=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 =cut