How do I do exception handling in a procedural language like C or Perl? (I know Perl also does OO.) What’s the best way to handle exception in procedural code in Perl?
It rather depends what you mean by 'exception handling'.
Some OS have exception mechanisms - see Windows exception handling routines or Linux signal handlers ( some of which are exceptions ).
If you mean the idiom in user code to signal an error and unwind the stack cleaning up any allocated objects, then C code doesn't call destructors on allocated objects when the stack is unwound ( it just reclaims the memory ), so it's normal in C for functions to return a status value, and for the calling code to do any clean up required before returning.
I don't know Perl well, but Googling for "Perl exception handling" indicates it has both built in mechanisms equivalent to try/catch and modules which give "OO style" exception handling.
The traditional approach is to... well... not to :)
Generally, people just check the status after attempting something, and if something went wrong, they clean up and return an error code, rather than continuing. This error checking and returning propagates back down the function call stack until it gets back to somewhere high-level in your program, where you care to inform the user instead of just returning more errors:
int try_it() {
if (!do_something(...)) {
return TRYIT_FAILURE;
}
}
void my_gui() {
rc = try_it()
if (rc == TRYIT_FAILURE) {
message_box("failed when trying it.", MB_ABORT|MB_RETRY);
}
...
}
You can also do this in a big function, using nested ifs, if you want something like a try...except construct:
if (stage1()) {
if (stage2()) {
if(stage3()) {
printf("success!\n");
} else {
// error handling for stage 3
}
} else {
// error handling for stage 2
}
} else {
// error handling for stage 1
}
And you can do the same with gotos if you're a bit mad.
However, you can do actual exceptions, at least in C. C has two standard library calls for this sort of thing: setjmp and longjmp. With these, you can jump back through several function calls to a predetermined place, where you know the exception (jump) happened. See Setjmp.h#exception_handling on wikipedia for more on that.
It seems Perl does have a way of doing this, though it looks far from intuitive to me. Then again, I don't code in Perl :)
In C there was setjmp and longjump; essentially, setjmp() saves the current context in the stack, and longjmp() can unwind its way back to the point saved by setjmp(). The wikipedia entry on those fills in the details nicely.
In Perl 5 the exception handling is done using eval
and die
. You simply eval the body of code and if it dies, you can inspect $@
for the error. It’s not exactly this easy if you want to do it properly, which is why the various try/catch modules exist. You might be interested in Try::Tiny which has no dependencies and describes all the gotchas you have to deal with when using the naive eval exception handling. (Also see this blog post by Try::Tiny’s author.)
Here's an example of how I do exceptions in Perl (without using one of the Try modules):
use Carp;
use English qw( -no_match_vars );
do_something_needing_rollback_if_failed();
eval {
do_something_dangerous();
} or do {
# Exception was thrown by dangerous method
# Save the error:
my $error = $EVAL_ERROR;
# Try to rollback
eval { rollback(); }
or do { confess qq{Couldn't rollback: $EVAL_ERROR. Original error $error}; }
# Let's rethrow:
confess qq{Rolled back! Error was $error};
}
One of the more annoying parts of Perl exception handling is it uses a single variable to store any exception error, and it can get accidentally overwritten, so some defensive coding is required.
In Perl, as usual, there is more than one way to do it. However the community of developers have as a whole settled on the correct way to do most things.
I'm becoming a fan of Exception::Base myself.
If you want a lighter implementation then just use Carp.
You trap exceptions using an eval { ... }; if ($@) { ... }
construct.
With Exception::Base it offers a way to abstract out the if ($@) { ... }
construct for you. You will still need to use eval { ... }
though.