views:

259

answers:

7

Is it OK to assign to $! on an error in Perl?

E.g.,

if( ! (-e $inputfile))
{
      $! = "Input file $inputfile appears to be non-existent\n";
      return undef;
}

This way I can handle all errors at the top-level.

Thanks.

+2  A: 

Yes you can assign stuff(#'s) to $!, just be wary of where you do it so you don't mess up some other functions message.

+9  A: 

Well, the documentation says it's an indicator for system errors, basically. So I wouldn't assign to it, you just make your lib's users mad.

Use exceptions instead:

eval { # this ain't the evil eval
   # some code
   die $something;
}
if (my $err = $@) {
   # exception handling
}

Note that you can "throw", or die, with about anything you need..

Dave Vogt
The value in $! is not guaranteed to be anything unless it is checked right after a function call that a. failed and b. claims to use errno. So anyone who gets made is doing something wrong.
Chas. Owens
Just as an FYI, many people consider using "die" within a library a VERY treif (aka non-kosher :) thing.Use _error/_errors flags and appropriate accessor methods (ideally, one for fast true/false check and one for printing a list of errors). Preferably, make this on object level :)
DVK
@DVK I disagree strongly. Its preferable for a library to throw exceptions, just so long they really are exceptional and you document that's how you're going to handle errors and you're consistent about it. Why? The user of the library will want to know when there's an error. Most often they'll simply die if there is one. This leads to writing "$obj->method or die $obj->error" everywhere. You'll get lazy and forget and miss an error. Exceptions mean you only have to add special code in the places where you want to recover from the error. Use autodie for a while and you'll see.
Schwern
+12  A: 

If you assign to $!, it is placed in the system errno variable, which only takes numbers. So you can in fact do:

use Errno "EEXIST";
$! = EEXIST;
print $!;

and get the string value for a defined system error number, but you can't do what you want - setting it to an arbitrary string. Such a string will get you a Argument "..." isn't numeric in scalar assignment warning and leave errno set to 0.

The other problem is that $! may be changed by any system call. So you can only trust it to have the value you set until you do a print or just about anything else. You probably want your very own error variable.

ysth
+3  A: 

My Rabbi said "no!"

Dennis Palmer
I'm tempted to vote this up, but it is in fact kosher, so the answer is too wrong to vote up :-). The lesson here is not to trust the Rabbi on this unless he also knows Perl
Nathan Fellman
Thanks for seeing my lame attempt at humor! According to a Rabbi and the strict definition of kosher, assigning to $! isn't covered by a specific law of Judaism, so it isn't "kosher." However, it may be permissible for you gentiles.
Dennis Palmer
I'm expecting someone to edit the question title, so preemptively noting here that the original title was "Is it kosher to assign to $! in Perl?".
ysth
And the winner is ... Kind of a surprise there.
innaM
@Dennis, I think that things that aren't covered by law are kosher by default. For instance, there are no laws governing computers (except as far as using electricity on shabbat), smoking (again, except for lighting fire on shabbat) or sunbathing, yet they are all kosher. I think the same applies to assigning $! :-)
Nathan Fellman
@Nathan Fellman: sunbathing would probably need gender-segregated to be tznius, even if you consider the danger of skin cancer to be allowed as "shomer pasaim hashem".
ysth
+3  A: 

Setting $! is fine, but:

  • you should do it at the end of your function
  • you should return a different value that indicates that an error occurred
  • you should use your OS's errno values rather than strings for setting purposes
  • the checking code needs to check the value should do so immediately on failure of the function (and only if a failure is indicated by the function)

Something to keep in mind is that die uses the value in $! for its exit code (so long as it is not zero).

Chas. Owens
A: 

If you only have one variable to store errors, you'll have problems if you have more than one error occurring in your program before checking the status of your error variable. That's worth avoiding if you can help it.

Thankfully in Perl you can help it. A really nice solution is to use object-oriented exception handling from Error.pm. This module will allow you to write try/catch blocks, like this:

try {
    some code;
    code that might thrown an exception;
    more code;
    return;
}
catch Error with {
    my $ex = shift;   # Get hold of the exception object
    # handle the exception;
};

The CPAN documentation for the module is quite good, and there is a Perl.com article on the subject too.

James Thompson
I'll second the use of exceptions, but don't use Error. Use [Exception::Class](http://search.cpan.org/dist/Exception-Class/lib/Exception/Class.pm) instead, it's much more capable.
jplindstrom
A: 

$! has so many caveats, being a global variable which lots of functions assign to (some of them C functions which Perl calls), that I would simply throw an exception (which in Perl means dying) and let the user trap it if they care. So instead of writing:

$obj->foo or die $!;
$obj->bar or die $!;
$obj->baz or die $!;

or even

$obj->foo or die $obj->error;
$obj->bar or die $obj->error;
$obj->baz or die $obj->error;

you can just write

$obj->foo;
$obj->bar;
$obj->baz;

and know that if there is an error you'll be informed of it. Also anyone above you will be informed and can trap it. Since that's the most common case make it happen without the user needing to remember it and type it over and over and over again.

Should you want to ignore or recover from the error, simply use eval BLOCK.

eval { $obj->foo };   # don't care if it works or not
eval { $obj->bar } or do { ...something when it doesn't work... };

Since this is the exceptional case it should be where the user has to remember to add more code and does more work.

Examples of this approach include DBI's RaiseError flag, which is only off by default for backwards compatibility, and the wonderful autodie module.

Schwern