Exception::Base(3pm)

πŸ“¦
version 0.2501 Β· 3 versions
, in opensuse-leap 16.0

πŸ’– Exception::Base(3pm) β€” Lightweight exceptions πŸ’–

Okay bestie, welcome to Exception::Base(3pm) from User Contributed Perl Documentation. We’re talking lightweight exceptions, but make it fashion. πŸ’…

🌸 NAME 🌸

Exception::Base β€” Lightweight exceptions

πŸ¦‹ SYNOPSIS πŸ¦‹

Here’s the whole demo, bestie β€” code stays exactly as it is. No cap. ✨

# Use module and create needed exceptions
use Exception::Base
   'Exception::Runtime',              # create new module
   'Exception::System',               # load existing module
   'Exception::IO',          => {
       isa => 'Exception::System' },  # create new based on existing
   'Exception::FileNotFound' => {
       isa => 'Exception::IO',        # create new based on previous
       message => 'File not found',   # override default message
       has => [ 'filename' ],         # define new rw attribute
       string_attributes => [ 'message', 'filename' ],
   };                                 # output message and filename

# eval is used as "try" block
eval {
  open my $file, '/etc/passwd'
    or Exception::FileNotFound->throw(
          message=>'Something wrong',
          filename=>'/etc/passwd');
};
# syntax for Perl >= 5.10
use feature 'switch';
if ($@) {
  given (my $e = Exception::Base->catch) {
    when ($e->isa('Exception::IO')) { warn "IO problem"; }
    when ($e->isa('Exception::Eval')) { warn "eval died"; }
    when ($e->isa('Exception::Runtime')) { warn "some runtime was caught"; }
    when ($e->matches({value=>9})) { warn "something happened"; }
    when ($e->matches(qr/^Error/)) { warn "some error based on regex"; }
    default { $e->throw; } # rethrow the exception
  }
}
# standard syntax for older Perl
if ($@) {
  my $e = Exception::Base->catch;   # convert $@ into exception
  if ($e->isa('Exception::IO')) { warn "IO problem"; }
  elsif ($e->isa('Exception::Eval')) { warn "eval died"; }
  elsif ($e->isa('Exception::Runtime')) { warn "some runtime was caught"; }
  elsif ($e->matches({value=>9})) { warn "something happened"; }
  elsif ($e->matches(qr/^Error/)) { warn "some error based on regex"; }
  else { $e->throw; } # rethrow the exception
}

# $@ has to be recovered ASAP!
eval { die "this die will be caught" };
my $e = Exception::Base->catch;
eval { die "this die will be ignored" };
if ($e) {
   (...)
}

# the exception can be thrown later
my $e = Exception::Base->new;
# (...)
$e->throw;

# ignore our package in stack trace
package My::Package;
use Exception::Base '+ignore_package' => __PACKAGE__;

# define new exception in separate module
package Exception::My;
use Exception::Base (__PACKAGE__) => {
    has => ['myattr'],
};

# run Perl with changed verbosity for debugging purposes
$ perl -MException::Base=verbosity,4 script.pl

🌈 DESCRIPTION 🌈

Okay so this class implements a fully OO exception mechanism, and it’s giving the same energy as Exception::Class or Class::Throwable. It gives programmers a simple interface for declaring exception classes. Those classes can be thrown and caught. Each uncaught exception prints a full stack trace if the default verbosity is increased for debugging purposes. Periodt. πŸ’…

The features of Exception::Base are literally this iconic list:

  • fast implementation of the exception class
  • fully OO without closures and source code filtering
  • does not mess with $SIG{__DIE__} and $SIG{__WARN__}
  • no external run-time modules dependencies, requires core Perl modules only
  • the default behavior of exception class can be changed globally or just for the thrown exception
  • matching the exception by class, message or other attributes
  • matching with string, regex or closure function
  • creating automatically the derived exception classes (use in perlfunc interface)
  • easily expendable, see Exception::System class for example
  • prints just an error message or dumps full stack trace
  • can propagate (rethrow) an exception
  • can ignore some packages for stack trace output
  • some defaults (i.e. verbosity) can be different for different exceptions

πŸ‘‘ OVERLOADS πŸ‘‘

πŸ’– Boolean context πŸ’–

True value. See to_bool method.

eval { Exception::Base->throw( message=>"Message", value=>123 ) };
if ($@) {
   # the exception object is always true
}

πŸ”₯ Numeric context πŸ”₯

Content of attribute pointed by numeric_attribute attribute. See to_number method.

eval { Exception::Base->throw( message=>"Message", value=>123 ) };
print 0+$@;           # 123

πŸŽ€ String context πŸŽ€

Content of attribute which is combined from string_attributes attributes with additional information, depended on verbosity setting. See to_string method.

eval { Exception::Base->throw( message=>"Message", value=>123 ) };
print "$@";           # "Message at -e line 1.\n"

πŸ¦‹ ~~ πŸ¦‹

Smart matching operator. See matches method.

eval { Exception::Base->throw( message=>"Message", value=>123 ) };
print "Message" ~~ $@;                          # 1
print qr/message/i ~~ $@;                       # 1
print ['Exception::Base'] ~~ $@;                # 1
print 123 ~~ $@;                                # 1
print {message=>"Message", value=>123} ~~ $@;   # 1
⚠️ Warning: The smart operator requires that the exception object is a second argument.

πŸ’œ CONSTANTS πŸ’œ

πŸ“ ATTRS πŸ“

Declaration of class attributes as reference to hash.

The attributes are listed as name => {properties}, where properties is a list of attribute properties:

  • is β€” Can be 'rw' for read-write attributes or 'ro' for read-only attributes. The attribute is read-only and does not have an accessor created if 'is' property is missed.
  • default β€” Optional property with the default value if the attribute value is not defined.

The read-write attributes can be set with new constructor. Read-only attributes and unknown attributes are ignored.

The constant have to be defined in derived class if it brings additional attributes.

package Exception::My;
use base 'Exception::Base';

# Define new class attributes
use constant ATTRS => {
  %{Exception::Base->ATTRS},       # base's attributes have to be first
  readonly  => { is=>'ro' },                   # new ro attribute
  readwrite => { is=>'rw', default=>'blah' },  # new rw attribute
};

package main;
use Exception::Base ':all';
eval {
  Exception::My->throw( readwrite => 2 );
};
if ($@) {
  my $e = Exception::Base->catch;
  print $e->readwrite;                # = 2
  print $e->defaults->{readwrite};    # = "blah"
}

πŸŽ€ ATTRIBUTES πŸŽ€

Okay bestie, so class attributes here are literally just values sitting in a blessed hash, and they also show up as accessor methods. Two ways to reach the same thing, no cap.


πŸ’– message (rw, default: 'Unknown exception')

This is the whole personality of your exception β€” the message. It's the part that actually shows up in the string when you stringify the object. Periodt.

eval { Exception::Base->throw( message=>"Message" ); };
print $@->message if $@;

Plot twist: it can also be an array reference full of strings, and then the "perlfunc" from sprintf kicks in to build the message. Slay.

Exception::Base->throw( message => ["%s failed", __PACKAGE__] );

πŸ”’ value (rw, default: 0)

The numeric main-character energy of your exception. In numeric context, this is what the object evaluates to.

eval { Exception::Base->throw( value=>2 ); };
print "Error 2" if $@ == 2;

πŸ—£οΈ verbosity (rw, default: 2)

This one controls how extra the exception gets when it turns itself into a string. Iconic little dial. The vibes, level by level:

  • 0 β€” Empty string. Nothing. Silence. πŸ’€
  • 1 β€” Message.
  • 2 β€” Message at %s line %d.

Exactly the same as what die() spits out. Small detail bestie: it does not include the "at %s line %d." string when the message ends with a "\n" character. And this is the default, so it's what you get out of the box.

  • 3 β€” Full trace time:
Class: Message at %s line %d
        %c_ = %s::%s() called in package %s at %s line %d
        ...propagated in package %s at %s line %d.
...

This output has the full trace of the error stack, minus the first "ignore_level" lines, and minus the packages listed in the "ignore_package" and "ignore_class" settings.

  • 4 β€” Full trace of the error stack, no filtering. At this level the "ignore_level", "ignore_package" and "ignore_class" settings only matter for the first line of the exception's message.

If verbosity is undef, then the default verbosity for exception objects gets used instead. Gentle reminder.

If the verbosity you set through the constructor ("new" or "throw") is lower than 3, the full stack trace won't be collected at all. Frugal queen.

If the verbosity is lower than 2, the full system data (time, pid, tid, uid, euid, gid, egid) won't be collected either.

You can change this setting through the import interface:

use Exception::Base verbosity => 4;

Or flex it for the whole Perl interpreter instance, e.g. for debugging purposes:

sh$ perl -MException::Base=verbosity,4 script.pl

πŸ™ˆ ignore_package (rw)

Holds the name (scalar or regexp) or names (as a reference array) of packages that get ignored in the error stack trace. Super useful when some package throws an exception but this module shouldn't be listed in the stack trace. We don't need that kind of negativity in the call stack.

package My::Package;
use Exception::Base;
sub my_function {
  do_something() or throw Exception::Base ignore_package=>__PACKAGE__;
  throw Exception::Base ignore_package => [ "My", qr/^My::Modules::/ ];
}

Can be changed with the import interface too:

use Exception::Base ignore_package => __PACKAGE__;

πŸ«₯ ignore_class (rw)

Holds the name (scalar) or names (as a reference array) of packages that are base classes for ignored packages in the error stack trace. Translation: some packages get ignored even when the derived class was the one called. Sneaky but valid.

package My::Package;
use Exception::Base;
Exception::Base->throw( ignore_class => "My::Base" );

Changed via the import interface:

use Exception::Base ignore_class => "My::Base";

πŸͺœ ignore_level (rw)

The number of levels on the stack trace to ignore. Handy when some package throws an exception but this module shouldn't be listed in the stack trace. Works with or without the ignore_package attribute β€” your call, bestie.

# Convert warning into exception. The signal handler ignores itself.
use Exception::Base 'Exception::My::Warning';
$SIG{__WARN__} = sub {
  Exception::My::Warning->throw( message => $_[0], ignore_level => 1 );
};

⏰ time (ro)

The timestamp of when the exception got thrown. Collected if the verbosity at throw time was greater than 1.

eval { Exception::Base->throw( message=>"Message" ); };
print scalar localtime $@->time;

πŸ†” pid (ro)

The PID of the Perl process at the moment of the thrown exception. Collected if the verbosity at throw time was greater than 1.

eval { Exception::Base->throw( message=>"Message" ); };
kill 10, $@->pid;

🧡 tid (ro)

The tid of the thread, or undef if threads aren't being used. Collected if the verbosity at throw time was greater than 1.

πŸ‘€ uid (ro), euid (ro), gid (ro), egid (ro)

The real and effective uid and gid of the Perl process at the moment of the thrown exception. All collected if the verbosity at throw time was greater than 1. Group project, all four of them.

πŸ₯ž caller_stack (ro)

The error stack as an array of arrays, each row stuffed with info about caller functions. The first 8 elements of each row are the same as the first 8 elements of the "caller" function's output. Anything past that is optional and consists of the arguments of the called function. Collected if the verbosity at throw time was greater than 1, and it only contains the first element of the caller stack if verbosity was lower than 3.

Also, if the arguments of the called function are references and the "Scalar::Util::weaken" function is available, then the reference gets weakened. Considerate king behavior.

eval { Exception::Base->throw( message=>"Message" ); };
($package, $filename, $line, $subroutine, $hasargs, $wantarray,
$evaltext, $is_require, @args) = $@->caller_stack->[0];

πŸ“’ propagated_stack (ro)

An array of arrays used for generating the "...propagated at" message. Each row's elements are the same as the first 3 elements of the "caller" function's output.

βœ‚οΈ max_arg_len (rw, default: 64)

The maximum length of an argument for functions in backtrace output. Zero means no limit at all β€” go off, length queen.

sub a { Exception::Base->throw( max_arg_len=>5 ) }
a("123456789");

πŸ”’ max_arg_nums (rw, default: 8)

The maximum number of arguments for functions in backtrace output. Zero means no limit on arguments.

sub a { Exception::Base->throw( max_arg_nums=>1 ) }
a(1,2,3);

πŸ“ max_eval_len (rw, default: 0)

The maximum length of eval strings in backtrace output. Zero means no limit on length.

eval "Exception->throw( max_eval_len=>10 )";
print "$@";

🧬 defaults

Meta-attribute holding the list of default values. Very meta of it.

my $e = Exception::Base->new;
print defined $e->{verbosity}
  ? $e->{verbosity}
  : $e->{defaults}->{verbosity};

🎯 default_attribute (default: 'message')

Meta-attribute holding the name of the default attribute. This is the one that gets set for the one-argument throw method. This attribute actually has meaning for derived classes, bestie.

use Exception::Base 'Exception::My' => {
    has => 'myattr',
    default_attribute => 'myattr',
};

eval { Exception::My->throw("string") };
print $@->myattr;    # "string"

πŸ”’ numeric_attribute (default: 'value')

Meta-attribute holding the name of the attribute that contains the numeric value of the exception object. This is the one used to represent the exception in numeric context.

use Exception::Base 'Exception::My' => {
    has => 'myattr',
    numeric_attribute => 'myattr',
};

eval { Exception::My->throw(myattr=>123) };
print 0 + $@;    # 123

πŸͺž eval_attribute (default: 'message')

Meta-attribute holding the name of the attribute that gets filled if the error stack is empty. That attribute ends up containing the value of the $@ variable. Meaningful for derived classes.

use Exception::Base 'Exception::My' => {
    has => 'myattr',
    eval_attribute => 'myattr'
};

eval { die "string" };
print $@->myattr;    # "string"

🧡 string_attributes (default: ['message'])

Meta-attribute holding the array of names of attributes with a defined value, which get joined into the string returned by the "to_string" method. And if none of the attributes are defined? Then the string is created from the first default value of the attributes listed in the opposite order. Little reverse-order moment, iykyk.

use Exception::Base 'Exception::My' => {
    has => 'myattr',
    myattr => 'default',
    string_attributes => ['message', 'myattr'],
};

eval { Exception::My->throw( message=>"string", myattr=>"foo" ) };
print $@->myattr;    # "string: foo"

eval { Exception::My->throw() };
print $@->myattr;    # "default"

🌈 IMPORTS 🌈

Okay bestie, this is the part where you teach use Exception::Base some new tricks at compile time. Periodt. πŸ’…

  • use Exception::Base 'attribute' => value; β€” this changes the default value for that attribute. If the attribute name has no special prefix, its old default gets straight-up swapped for the new value. Out with the old, in with the new. πŸ¦‹
use Exception::Base verbosity => 4;

But if the attribute name starts with + or -, then the new value builds on top of the previous one instead of replacing it. It's giving "character development" ✨:

  • 🌸 If the original value was a reference to an array, the new value can be included in or removed from the original array. And if you need to add or remove more than one element, just hand it an array reference. Slay.
use Exception::Base
    "+ignore_packages" => [ __PACKAGE__, qr/^Moose::/ ],
    "-ignore_class" => "My::Good::Class";
  • 🌸 If the original value was a number, it gets incremented or decremented by the new value. Math but make it cute.
use Exception::Base "+ignore_level" => 1;
  • 🌸 If the original value was a string, the new value gets included (appended). Glow-up for strings, literally.
use Exception::Base "+message" => ": The incuded message";

  • use Exception::Base 'Exception', ...; β€” loads an additional exception class module for you. And if that module isn't available? No stress β€” it creates the exception class automatically at compile time, based on the Exception::Base class. Iconic auto-pilot behaviour. πŸ‘‘
use Exception::Base qw{ Exception::Custom Exception::SomethingWrong };
Exception::Custom->throw;
  • use Exception::Base 'Exception' => { isa => BaseException, version => version, ... }; β€” also loads an additional exception class module. But here's the vibe: if the module's version is lower than the given parameter, or the module can't be loaded at all, then it creates the exception class automatically at compile time. The newly created class is based on the given class and gets the given $VERSION variable. Bestie, it finesses it for you. πŸ”₯
    • isa β€” the newly created class will be based on the given class. Passing down the family traits πŸ’–
use Exception::Base
  'Exception::My',
  'Exception::Nested' => { isa => 'Exception::My };
  • version β€” the class will be created only if the module's version is lower than the given parameter, and it will have the version given in the argument. Version check but make it protective 🫢
use Exception::Base
  'Exception::My' => { version => 1.23 };
  • has β€” gives the class new attributes. If the parameter is a string, you get one new rw attribute. If it's a reference to an array of strings, you get multiple new rw attributes. And if it's a reference to a hash of array of strings (with rw and ro as hash keys), you get new rw or ro attributes. Choose your fighter. ⭐
use Exception::Base
  'Exception::Simple' => { has => 'field' },
  'Exception::More' => { has => [ 'field1', 'field2' ] },
  'Exception::Advanced' => { has => {
      ro => [ 'field1', 'field2' ],
      rw => [ 'field3' ]
  } };
  • message
  • verbosity
  • max_arg_len
  • max_arg_nums
  • max_eval_len
  • any other attribute having a default property β€” the class will simply have the default property for whatever attribute you named. Effortless. πŸŽ€
use Exception::Base
  'Exception::WithDefault' => { message => 'Default message' },
  'Exception::Reason' => {
      has => [ 'reason' ],
      string_attributes => [ 'message', 'reason' ] };

🧚 CONSTRUCTORS 🧚

  • new([%args]) β€” creates the exception object so you can throw it later. Heads up though: the system data attributes like time, pid, uid, gid, euid, egid are not filled in. They're on their own little vacation, ngl. πŸ™ˆ

If the key of the argument is a read-write attribute, that attribute gets filled. Otherwise? The argument is just ignored. Not every key gets to be main character energy, and that's okay. πŸ’…

$e = Exception::Base->new(
         message=>"Houston, we have a problem",
         unknown_attr => "BIG"
     );
print $e->{message};

Small flex for the performance girlies: the constructor reads the list of class attributes from the ATTRS constant function and stores it in the internal cache for performance reasons. The default values for the class are also stored in that internal cache. Efficient queen behavior. ⚑

  • CLASS->throw([%args]]) β€” creates the exception object and immediately throws it using the die system function. No chill, instant action. πŸ”₯
open my $fh, $file
  or Exception::Base->throw( message=>"Can not open file: $file" );

Fun fact: throw is also exported as a function, so you can just call it like a normal person:

open my $fh, $file
  or throw 'Exception::Base' => message=>"Can not open file: $file";
  • Oh and also β€” throw can be used as a method too. Versatile icon. πŸ‘‘

πŸ› οΈ METHODS πŸ› οΈ

okay bestie, this is the good part β€” all the stuff your exception object can actually do πŸ’…

  • $obj->throw([%args]) β€” throws the exception object, like, immediately. no waiting. it's also the move for rethrowing an existing exception object, and any extra args you pass will override the attributes on that existing object. slay.
  $e = Exception::Base->new;
  # (...)
  $e->throw( message=>"thrown exception with overridden message" );

  eval { Exception::Base->throw( message=>"Problem", value=>1 ) };
  $@->throw if $@->value;
  • $obj->throw(message, [%args]) β€” so if the number of args in the list is odd, the first argument is a message. and heads up: that message can be overridden by a message from the args list. lowkey chaotic but iconic.
  Exception::Base->throw( "Problem", message=>"More important" );
  eval { die "Bum!" };
  Exception::Base->throw( $@, message=>"New message" );
  • CLASS->throw($exception, [%args]) β€” rethrows an existing exception object as a different exception class, right away. glow-up via reclassification ✨
  eval { open $f, "w", "/etc/passwd" or Exception::System->throw };
  # convert Exception::System into Exception::Base
  Exception::Base->throw($@);
  • CLASS->catch([$variable]) β€” recovers the exception from the variable argument, or from the $@ variable if that argument was empty. then it also replaces $@ with an empty string so you don't get stuck in an endless loop. not me preventing infinite loops πŸ’€

The method returns an exception object if an exception was caught, or an undefined value otherwise.

  eval { Exception::Base->throw; };
  if ($@) {
      my $e = Exception::Base->catch;
      print $e->to_string;
  }

And if the value is not empty but does not contain an Exception::Base object? a new exception object gets created with class CLASS, and its message is based on the previous value with the " at file line 123." string removed, plus the last end of line (LF) stripped too.

  eval { die "Died\n"; };
  my $e = Exception::Base->catch;
  print ref $e;   # "Exception::Base"
  • matches(that) β€” checks whether the exception object matches whatever you handed it. the matches method overloads the ~~ smart matching operator, btw. ⚠️ Warning: the second argument for the smart matching operator needs to be a scalar. just scalar. that's the vibe check.

If the argument is a reference to an array, it checks whether the object is a given class:

  use Exception::Base
    'Exception::Simple',
    'Exception::Complex' => { isa => 'Exception::Simple };
  eval { Exception::Complex->throw() };
  print $@->matches( ['Exception::Base'] );                    # matches
  print $@->matches( ['Exception::Simple', 'Exception::X'] );  # matches
  print $@->matches( ['NullObject'] );                         # doesn't

If the argument is a reference to a hash, the attributes of the exception object are what get matched:

  eval { Exception::Base->throw( message=>"Message", value=>123 ) };
  print $@->matches( { message=>"Message" } );             # matches
  print $@->matches( { value=>123 } );                     # matches
  print $@->matches( { message=>"Message", value=>45 } );  # doesn't

If the argument is a single string, a regexp, a code reference, or it's undefined, then the default attribute of the exception object gets matched (usually that's the message attribute, iykyk):

  eval { Exception::Base->throw( message=>"Message" ) };
  print $@->matches( "Message" );                          # matches
  print $@->matches( qr/Message/ );                        # matches
  print $@->matches( qr/[0-9]/ );                          # doesn't
  print $@->matches( sub{/Message/} );                     # matches
  print $@->matches( sub{0} );                             # doesn't
  print $@->matches( undef );                              # doesn't

If the argument is a numeric value, it matches when the value attribute matches:

  eval { Exception::Base->throw( value=>123, message=>456 ) } );
  print $@->matches( 123 );                                # matches
  print $@->matches( 456 );                                # doesn't

And if an attribute contains an array reference, that array gets sprintf-ed before matching. cute little preprocessing moment πŸŽ€

  eval { Exception::Base->throw( message=>["%s", "Message"] ) };
  print $@->matches( "Message" );                          # matches
  print $@->matches( qr/Message/ );                        # matches
  print $@->matches( qr/[0-9]/ );                          # doesn't

The match method matches for special keywords too β€” these are the VIP keys:

  • -isa β€” matches if the object is a given class.
    eval { Exception::Base->new( message=>"Message" ) };
    print $@->matches( { -isa=>"Exception::Base" } );            # matches
    print $@->matches( { -isa=>["X::Y", "Exception::Base"] } );  # matches
  • -has β€” matches if the object has a given attribute.
    eval { Exception::Base->new( message=>"Message" ) };
    print $@->matches( { -has=>"Message" } );                    # matches
  • -default β€” matches against the default attribute, usually the message attribute.
    eval { Exception::Base->new( message=>"Message" ) };
    print $@->matches( { -default=>"Message" } );                # matches
  • to_string β€” returns the string representation of the exception object. it gets called automatically whenever the exception object is used in string scalar context, but you can also totally call it yourself, no gatekeeping.
  eval { Exception::Base->throw; };
  $@->{verbosity} = 1;
  print "$@";
  $@->verbosity = 4;
  print $@->to_string;
  • to_number β€” returns the numeric representation of the exception object. auto-called when the object lands in numeric scalar context. explicit calls work too πŸ’–
  eval { Exception::Base->throw( value => 42 ); };
  print 0+$@;           # 42
  print $@->to_number;  # 42
  • to_bool β€” returns the boolean representation of the exception object. auto-called in boolean context, and yes you can call it straight up.
  eval { Exception::Base->throw; };
  print "ok" if $@;           # ok
  print "ok" if $@->to_bool;  # ok
  • get_caller_stacktrace β€” returns an array of strings, or a string, with the caller stack trace. it's implicitly used by the to_string method. the receipts, basically 🧾
  • PROPAGATE β€” checks the caller stack and fills the propagated_stack attribute. usually this one comes out when the die system function was called without any arguments. messy but valid.
  • _collect_system_data β€” collects system data and fills the attributes of the exception object. this method is called automatically if the exception is thrown or created by the new constructor. and it can be overridden by a derived class, which is lowkey the whole point πŸ‘‘
  package Exception::Special;
  use base 'Exception::Base';
  use constant ATTRS => {
    %{Exception::Base->ATTRS},
    'special' => { is => 'ro' },
  };
  sub _collect_system_data {
    my $self = shift;
    $self->SUPER::_collect_system_data(@_);
    $self->{special} = get_special_value();
    return $self;
  }
  BEGIN {
    __PACKAGE__->_make_accessors;
  }
  1;

The method returns the reference to the self object. πŸͺž

  • _make_accessors β€” creates accessors for each attribute. this is a static method, and it should be called in each derived class that defines new attributes. don't skip it, fr fr.
  package Exception::My;
  # (...)
  BEGIN {
    __PACKAGE__->_make_accessors;
  }
  • package β€” returns the package name of the subroutine which thrown an exception.
  • file β€” returns the file name of the subroutine which thrown an exception.
  • line β€” returns the line number for the file of the subroutine which thrown an exception.
  • subroutine β€” returns the subroutine name which thrown an exception.

πŸ‘€ SEE ALSO πŸ‘€

Repository: <http://github.com/dex4er/perl-Exception-Base> β€” go give it a star bestie ⭐

There are more implementations of exception objects available on CPAN, if you're shopping around. but first, please note that Perl has a built-in implementation of pseudo-exceptions:

  eval { die { message => "Pseudo-exception", package => __PACKAGE__,
               file => __FILE__, line => __LINE__ };
  };
  if ($@) {
    print $@->{message}, " at ", $@->{file}, " in line ", $@->{line}, ".\n";
  }

The more complex implementation of exception mechanism provides more features. here's the roster:

  • Error β€” complete implementation of try/catch/finally/otherwise mechanism. it uses nested closures with a lot of syntactic sugar. it's slightly faster than the Exception::Base module for the failure scenario, and much slower for the success scenario. it doesn't provide a simple way to create user defined exceptions. it doesn't collect system data and stack trace on error. πŸ™ˆ
  • Exception::Class β€” more Perlish way to do OO exceptions. it's similar to the Exception::Base module and provides similar features, but it is 10x slower for the failure scenario. ten times. πŸ’€
  • Exception::Class::TryCatch β€” additional try/catch mechanism for Exception::Class. it is 15x slower for the success scenario.
  • Class::Throwable β€” elegant OO exceptions, similar to Exception::Class and Exception::Base. it might be missing some features found in Exception::Base and Exception::Class.
  • Exceptions β€” not recommended. abandoned. modifies %SIG handlers. 🚩
  • TryCatch β€” a module which gives new try/catch keywords without a source filter.
  • Try::Tiny β€” smaller, simpler and slower version of the TryCatch module.

The Exception::Base does not depend on other modules like Exception::Class, and it is more powerful than Class::Throwable. also it does not use closures like Error, and it does not pollute the namespace like Exception::Class::TryCatch. it is also much faster than Exception::Class::TryCatch and Error for the success scenario. main character energy, honestly πŸ‘‘

The Exception::Base is compatible with syntax sugar modules like TryCatch and Try::Tiny. πŸ“

The Exception::Base is also a base class for enhanced classes:

  • Exception::System β€” the exception class for system or library calls which modifies the $! variable.
  • Exception::Died β€” the exception class for eval blocks with simple die in perlfunc. it can also handle the $SIG{__DIE__} hook and convert a simple die in perlfunc into an exception object.
  • Exception::Warning β€” the exception class which handles the $SIG{__WARN__} hook and converts a simple warn in perlfunc into an exception object. πŸŽ€

🌸 EXAMPLES 🌸

πŸŽ€ New exception classes πŸŽ€

The Exception::Base module lets you create new exception classes so easily, bestie. You can use the import in perlfunc interface or the base module to do it.

The import in perlfunc interface lets you create a new class with new read-write attributes. Slay:

package Exception::Simple;
use Exception::Base (__PACKAGE__) => {
  has => qw{ reason method },
  string_attributes => qw{ message reason method },
};

For more complex exceptions you can redefine the ATTRS constant. It's giving main-character customization:

package Exception::Complex;
use base 'Exception::Base';
use constant ATTRS => {
  %{ Exception::Base->ATTRS },     # SUPER::ATTRS
  hostname => { is => 'ro' },
  string_attributes => qw{ hostname message },
};
sub _collect_system_data {
  my $self = shift;
  my $hostname = `hostname`;
  chomp $hostname;
  $self->{hostname} = $hostname;
  return $self->SUPER::_collect_system_data(@_);
}

πŸ”₯ PERFORMANCE πŸ”₯

There are two scenarios for eval in perlfunc block: success or failure. The success scenario should have no penalty on speed. The failure scenario is usually more complex to handle and can be significantly slower. 😭

Any other code than simple if ($@) is really slow and shouldn't be used if speed is important. That means any module which provides try/catch syntax sugar should be avoided: Error, Exception::Class::TryCatch, TryCatch, Try::Tiny. Ngl, avoid the extra sprinkle if you're chasing speed.

Be careful because simple if ($@) has many gotchas which are described in Try::Tiny's documentation. 🫢

The Exception::Base module was benchmarked with other implementations for simple try/catch scenario. The results (Perl 5.10.1 x86_64-linux-thread-multi) are following:

  • eval/die string β€” success: 3715708 sub/s, failure: 408951 sub/s
  • eval/die object β€” success: 4563524 sub/s, failure: 191664 sub/s
  • Exception::Base eval/if β€” success: 4903857 sub/s, failure: 11291 sub/s
  • Exception::Base eval/if verbosity=1 β€” success: 4790762 sub/s, failure: 18833 sub/s
  • Error β€” success: 117475 sub/s, failure: 26694 sub/s
  • Class::Throwable β€” success: 4618545 sub/s, failure: 12678 sub/s
  • Exception::Class β€” success: 643901 sub/s, failure: 3493 sub/s
  • Exception::Class::TryCatch β€” success: 307825 sub/s, failure: 3439 sub/s
  • TryCatch β€” success: 690784 sub/s, failure: 294802 sub/s
  • Try::Tiny β€” success: 268780 sub/s, failure: 158383 sub/s

The Exception::Base module was written to be as fast as it is possible. It does not use internally i.e. accessor functions which are slower about 6 times than standard variables. It is slower than pure die/eval for success scenario because it uses OO mechanisms which are slow in Perl. It can be a little faster if some features are disabled, i.e. the stack trace and higher verbosity. πŸ’…

You can find the benchmark script in this package distribution. ✨


πŸ› BUGS πŸ›

If you find the bug or want to implement new features, please report it at <https://github.com/dex4er/perl-Exception-Base/issues>.

The code repository is available at <http://github.com/dex4er/perl-Exception-Base>.


πŸ‘‘ AUTHOR πŸ‘‘

Piotr Roszatycki <dexter@cpan.org>


πŸ“œ LICENSE πŸ“œ

Copyright (c) 2007-2015 Piotr Roszatycki <dexter@cpan.org>.

This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

See <http://dev.perl.org/licenses/artistic.html>.


Okay bestie, that's the whole page! πŸ’– This one comes from perl v5.42.0, dated 2023-08-08, and the page is Exception::Base(3pm). Byeeee! πŸŽ€βœ¨πŸ¦‹

πŸ“¦ where this page comes from