Fork me on GitHub

Project Notes

#451 Carp

About using the Carp module for error handling in Perl

Notes

The Carp (Perl module) is Perl’s standard error-reporting helper that improves on plain warn() and die() by reporting problems from the caller’s perspective rather than where the error occurred internally. It’s mainly used in modules so that users see errors as if they originated in their own code, not deep inside library internals.

Key functions:

  • carp – like warn, but reports the error at the caller’s location (short message, minimal stack)
  • croak – like die, but reported at the caller’s location (short message, minimal stack)
  • cluck – like carp, but includes a full stack trace
  • confess – like croak, but with a full stack trace

Demonstration

The examples.sh script calls all the demonstrations supported by the example.pl Perl script and reports the exit code:

$ ./examples.sh
Running carp examples...

## Demonstrating: carp
This is a warning message. (carp: warning message from perspective of caller) at example.pl line 66.
shortmess: at example.pl line 66.
longmess: at example.pl line 41.
        Demo::demoCarpModule("carp") called at example.pl line 66
Exit code: 0

## Demonstrating: cluck
This is a warning message with stack trace. (cluck: like carp but with stack trace) at example.pl line 22.
        Demo::demoCluck() called at example.pl line 41
        Demo::demoCarpModule("cluck") called at example.pl line 66
shortmess: at example.pl line 66.
longmess: at example.pl line 41.
        Demo::demoCarpModule("cluck") called at example.pl line 66
Exit code: 0

## Demonstrating: croak
This is an error message. (croak: error message from perspective of caller) at example.pl line 66.
Exit code: 255

## Demonstrating: confess
This is an error message. (confess: like croak but with stack trace) at example.pl line 32.
        Demo::demoConfess() called at example.pl line 41
        Demo::demoCarpModule("confess") called at example.pl line 66
Exit code: 255

## Demonstrating: read-file-ok
text file content
Exit code: 0

## Demonstrating: read-file-bad
Cannot locate file! at example.pl line 66.
Exit code: 2

## Demonstrating: settings
Carp settings:
  Carp::MaxEvalLen: 0
  Carp::MaxArgLen: 64
  Carp::MaxArgNums: 8
  Carp::Verbose: 0
Exit code: 0

Example Code

The example.pl Perl script:

#!/usr/bin/env perl
#
package Demo;
use Carp;
use Carp qw(cluck longmess shortmess);

sub readFile {
 my $filename = shift(@_);
 open(FILE, $filename) or croak("Cannot locate file!");
 print <FILE>;
 close FILE;
}

sub demoCarp {
  carp "This is a warning message. (carp: warning message from perspective of caller)";
  print shortmess("shortmess:");
  print longmess("longmess:");
}

sub demoCluck {
  cluck "This is a warning message with stack trace. (cluck: like carp but with stack trace)";
  print shortmess("shortmess:");
  print longmess("longmess:");
}

sub demoCroak {
  croak "This is an error message. (croak: error message from perspective of caller)";
}

sub demoConfess {
  confess "This is an error message. (confess: like croak but with stack trace)";
}

sub demoCarpModule {
  my $method = shift(@_);

  print "\n## Demonstrating: $method\n";

  if ($method eq 'carp') {
    demoCarp();
  } elsif ($method eq 'croak') {
    demoCroak();
  } elsif ($method eq 'cluck') {
    demoCluck();
  } elsif ($method eq 'confess') {
    demoConfess();
  } elsif ($method eq 'read-file-ok') {
    readFile('data.txt');
  } elsif ($method eq 'read-file-bad') {
    readFile('data.txt.missing');
  } elsif ($method eq 'settings') {
    print "Carp settings:\n";
    print "  Carp::MaxEvalLen: ", $Carp::MaxEvalLen, "\n";
    print "  Carp::MaxArgLen: ", $Carp::MaxArgLen, "\n";
    print "  Carp::MaxArgNums: ", $Carp::MaxArgNums, "\n";
    print "  Carp::Verbose: ", $Carp::Verbose, "\n";
  } else {
    print "Unknown argument. Use 'carp', 'croak', 'confess', 'cluck', 'read-file-ok', 'read-file-bad', or 'settings'.\n";
  }
}

package main;
my $arg = shift(@ARGV) // 'carp';
Demo::demoCarpModule($arg);

1;

The example.sh script is a wrapper to call all the examples and report the process exit code:

#!/usr/bin/env bash

function runExample() {
  local method="$1"
  perl example.pl "$method"
  local rc=$?
  echo "Exit code: $rc"
}

echo "Running carp examples..."
runExample "carp"
runExample "cluck"
runExample "croak"
runExample "confess"
runExample "read-file-ok"
runExample "read-file-bad"
runExample "settings"

The Difference Between carp and cluck

The cluck method is like carp but with full stack trace.

But consider the following example carp-cluck-a.pl:

#!/usr/bin/env perl
#
use Carp qw(carp cluck);

sub c {
    carp "carp";
    cluck "cluck";
}
sub a { b() }
sub b { c() }

a();

It shows carp and cluck essentially behaving identically:

$ ./carp-cluck-a.pl
carp at ./carp-cluck-a.pl line 6.
        main::c() called at ./carp-cluck-a.pl line 10
        main::b() called at ./carp-cluck-a.pl line 9
        main::a() called at ./carp-cluck-a.pl line 12
cluck at ./carp-cluck-a.pl line 7.
        main::c() called at ./carp-cluck-a.pl line 10
        main::b() called at ./carp-cluck-a.pl line 9
        main::a() called at ./carp-cluck-a.pl line 12

This is because while carp tries to stop at the first “external” caller, but in simple or single-package call stacks it may include as many frames as cluck. Older versions of Carp were more aggressive about stopping early, but this is the modern behaviour (I am using v5.42.2).

If we modify the example to make the calls across module boundaries per carp-cluck-b.pl:

#!/usr/bin/env perl
#
package My::Layer;
use Carp qw(carp cluck);
sub c {
    carp "carp";
    cluck "cluck";
}

package main;
sub a { b() }
sub b { My::Layer::c() }

a();

Now we see distinct behaviour:

$ ./carp-cluck-b.pl
carp at ./carp-cluck-b.pl line 12.
cluck at ./carp-cluck-b.pl line 7.
        My::Layer::c() called at ./carp-cluck-b.pl line 12
        main::b() called at ./carp-cluck-b.pl line 11
        main::a() called at ./carp-cluck-b.pl line 14

Testing Carp Behaviour

How to test carp behaviour? Let’s demonstrate using Test::Warn, installed as follows:

cpan Test::Warn

Tests are demonstrated with carping.t:

use Test::More 'no_plan';
use Test::Warn;
use Carp;

sub add_positives
{
  my ( $l, $r ) = @_;
  carp "first argument ($l) was negative"  if $l < 0;
  carp "second argument ($r) was negative" if $r < 0;
  croak "second argument was zero" if $r == 0;
  return $l + $r;
}

warning_like { is( add_positives( 8, -3 ), 5 ) } qr/negative/;

warnings_like { is( add_positives( -8, -3 ), -11 ) }
  [ qr/first.*negative/, qr/second.*negative/ ];

eval { add_positives( 8, 0 ) };
ok( $@, 'second argument was zero' ) ;

Test results:

$ prove carping.t
carping.t .. ok
All tests successful.
Files=1, Tests=5,  0 wallclock secs ( 0.00 usr  0.00 sys +  0.01 cusr  0.00 csys =  0.01 CPU)
Result: PASS

Credits and References

About LCK#451
Perl

This page is a web-friendly rendering of my project notes shared in the LittleCodingKata GitHub repository.

Project Source on GitHub Return to the LittleCodingKata Catalog
About LittleCodingKata

LittleCodingKata is my collection of programming exercises, research and code toys broadly spanning things that relate to programming and software development (languages, frameworks and tools).

These range from the trivial to the complex and serious. Many are inspired by existing work and I'll note credits and references where applicable. The focus is quite scattered, as I variously work on things new and important in the moment, or go back to revisit things from the past.

This is primarily a personal collection for my own edification and learning, but anyone who stumbles by is welcome to borrow, steal or reference the work here. And if you spot errors or issues I'd really appreciate some feedback - create an issue, send me an email or even send a pull-request.

Follow the Blog follow projects and notes as they are published in your favourite feed reader