This is the intended behavior from Carp
[...] use carp()
or croak()
which report the error as being from where your module was called. [...] There is no guarantee that that is where the error was, but it is a good educated guess.
So the error is reported at where the module's sub is called, which is what the user wants
use warnings;
use strict;
use feature 'say';
use Try::Tiny;
package Throw {
use warnings;
use Carp qw(croak confess);
#sub bam { die "die in module" }; # l.11
sub bam { croak "croak in module" };
1;
};
try {
Throw::bam(); # l.17
}
catch {
say "caught one: $_";
die "die in catch: $_";
};
say "done";
Prints
caught one: croak in module at exceptions.pl line 17.
die in catch: croak in module at exceptions.pl line 17.
If the sub throws using die
then this is reported at line 11
, what is the normal behavior of die, and what you seem to expect.
If any of this is unclear or suboptimal then better use confess
and nicely get a full stacktrace. Also, if you wish more exception-based-like code behavior, can put together an exception/error class and throw its object,† designed and populated as desired.
If you want to confess
an object note that at this time Carp has limits with that
The Carp
routines don't handle exception objects currently. If called with a first argument that is a reference, they simply call die()
or warn()
, as appropriate.
One way then would be to confess
a stringification of the object,‡ getting at least both a full stack backtrace and whatever is in the object.
I get the same behavior with eval
, by replacing try-catch and $_
above
eval {
Throw::bam();
};
if ($@) {
say "caught one: $@";
die "die in catch: $@";
};
Prints exactly the same as above
While the above is clear and behaves as expected, a weird thing is indeed seen in the question's example: the error is reported from the whole try-catch statement, ie. at its closing brace, where line 10 is. (The try
sub is prototyped and the whole try-catch is a syntax aid equivalent to a call to try
that takes an anonymous sub, and then perhaps more. See ikegami's comment, and docs. Also see this post for more about its syntax.)
This is strange since the call to the croaking sub is foo()
inside the try
statement and this line should be reported, what can be confirmed by running the script with -MCarp::Always
. But in the code in this answer the line of the call to Throw::bam
is indeed reported -- why this difference?
The clear purpose of croak
is to be used in the libraries, so that the user can see at which point in their (user's) code they called the library in a way that triggered an error. (While die
would point to the place where error is detected, so in the library, most likely useless to the user. But read die
and Carp
docs for related complexities.)
What isn't obvious is that when croak
is emitted in the same namespace (main::foo()
) from try-catch
in its own namespace (Try::Tiny
) things get confused, and the end of its statement is reported. This can be checked by adding a foo()
to my code above and calling it (instead of a sub from a module), and we get the question's behavior reproduced.
This doesn't happen if main::foo()
with croak
inside is called from a (complex) statement right in main::
, so it seems to be due to the try-catch mix up of namespaces. (On the other hand, try-catch sugar adds an anonymous sub to the callstack, and this sure can mix things up as well.)
In practical terms, I'd say: always use croak
out of modules (otherwise use die
), or, better yet if you want to mimic exception-based code, use confess
and/or your exception class hierarchy.
† Even just like die ExceptionClass->new(...);
Bear in mind that in the way of exceptions Perl only has the lonesome die
, and eval
. For more structure you'll need to implement it all, or use frameworks like Exception::Class or Throwable
‡ By writing and using a method that generates a plain string with useful information from the object, for Carp::confess $obj->stringify
.
Or by overloading the ""
(quote) operator for the class since it gets used when confess
-ing an object (string context), for Carp::confess $obj
; this is good to have anyway.
A basic example for both:
use overload ( q("") => \&stringify );
sub stringify {
my $self = shift;
join ", ", map { "$_ => " . ( $self->{$_} // 'undef' ) } keys %$self
}
where instead of a reference to a named sub on can directly write an anonymous sub
.