3

I had seen some exceptions that pointed to (end of) the catch block itself (see the example below).

As my opinion, this is an unexpected behavior, since it alters the location of original exception and make difficult to debug (it should say die at line 13.)

It shows the (correct) line 13, if I use die/confess or using eval instead try-catch.

Not knowing how my code will be called within the stack, I started to avoid using croak now. What do you think? Did I get right, or there is a way to improve this?

Best regards, Steve

use Carp;
use Try::Tiny;

try {
  foo();
} 
catch {
  # do something before die
  die $_;
};             # this is line 10

sub foo {
  croak 'die'; # this is line 13
}

Output: die at line 10.

Steve S.
  • 71
  • 6
  • Perhaps you want `croak()` in your catch block and `die()` in your `foo{}` ? – clamp Mar 08 '21 at 10:15
  • @clamp, ideally yes, you are right, but sometime you are developing the foo, and you think: hmm... I must croak if input neq 'bla' without knowing it will be catched later. – Steve S. Mar 08 '21 at 11:03
  • If you do not mind switching module, you can use Nice::Try instead as I suggested in my [reply](https://stackoverflow.com/a/71655295/4814971) and it will work as expected. – Jacques Mar 29 '22 at 01:02

2 Answers2

4

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.

zdim
  • 64,580
  • 5
  • 52
  • 81
  • Re "*So the error is reported at where the module's sub is called*", And the caller is deemed to be the try-catch statement rather than the `foo()` in the anon sub. That bit is is odd. – ikegami Mar 09 '21 at 04:44
  • @ikegami Yeah, I am going to look into this far more (in particular into what the sugar try-catch does by adding to the callstack). In their example I think that it has the whole statement as one "line" and so it can't report the foo()'s block separately? In my code it does report it, perhaps as it doesn't "inline" (in the sense of line-counting) the call `Throw::pop` – zdim Mar 09 '21 at 05:41
  • It's not a merely a block; it's an anon sub. The code is equivalent to `try(sub { foo(); }, catch(sub { ... }));`. (You can literally use that!) And the call to `foo()` is its own statement with its own line number. I think it's because the `croak` and the call to `foo` is in the same namespace, and `try` isn't. `perl -MCarp::Always ...` would confirm this. – ikegami Mar 09 '21 at 05:56
  • yeah, that's what it is. `croak` is really meant to be used in code called (directly or indirectly) from other namespaces. The fact that `try` is calling the anon sub from another namespace is messing with things. – ikegami Mar 09 '21 at 05:56
  • Many thanks @ikegami for the discussion and input thoughts. If only eval behaves the same (false) way as try catch, then I would agree with the statement to avoid using croak in the same namespace. Unfortunately eval does report the correct line. This inconsistency leads to confusion on what to use what. Do you guys see any possibility for try catch to be corrected? – Steve S. Mar 11 '21 at 12:10
  • @SteveS. I've never found enough reason to go to `Try::Tiny` for a little syntax fix (which comes with a few issues of its own, and adding an anon to the callstack in particular is one of them). So I've always been happy with `eval`, in Perl. But you definitely want to use `croak` out of modules. I wouldn't hold my breath over this getting fixed, in the first place since I fear that the mixup with namespaces might be a bit complex, perhaps involving that anon. (And `Carp` itself surely isn't sugar.) – zdim Mar 11 '21 at 20:03
  • @SteveS. There's nothing to correct. The whole point of croak is to create error messages that have the line number of the caller. It is showing the correct line number. It's not ever supposed to produce the same output as die (or you would just use die). – ikegami Mar 12 '21 at 01:46
1

As a way of solving the OP's problem, but with a different module, if you use Nice::Try instead, you will get the result you expect:

use Carp;
use Nice::Try;

try {
  foo();
} 
catch {
  # do something before die
  die $_;
}             # this is line 10

sub foo {
  croak 'die'; # this is line 13
}

You get:

die at ./try-catch-and-croak.pl line 13.
    main::foo() called at ./try-catch-and-croak.pl line 4
    main::__ANON__ called at ./try-catch-and-croak.pl line 7
    eval {...} called at ./try-catch-and-croak.pl line 7    ...propagated at ./try-catch-and-croak.pl line 9.

For full disclosure, I am the author behind Nice::Try

Jacques
  • 991
  • 1
  • 12
  • 15