in reply to When to use eval
Should you always wrap in eval functions imported from modules?
Only if you expect exceptions from it, AND you want to catch that exception and do something (such as allow the program to continue on) instead of having the program crash.
What does eval catch? A function that issues a die? A function that just returns an error?
It catches exceptions. A function that returns an error is not an exception. Since it's returning something, it was programmed to do that. It didn't crash. If code returns an error status, it means that although there was an error, it wasn't fatal, and now your code must decide what to do based on the error.
When someone puts a die or croak or whatever into their code, it typically means that "my code was not designed to handle this situation, and my code can't continue to function properly so I'm bailing out immediately". It's up to you to decide whether the failure of someone elses code will prevent yours from continuing to do what it needs to do. If the underlying code dies, but your code can follow another code path without the results of that code, then use eval to catch that situation, and direct your code appropriately.
Here are a couple of examples. This first one runs a while loop that tries to create a shared memory segment using random keys until it can create one. If it tries to create a segment but it collides with an existing one, the underlying code will die as that's definitely a fatal error. What this code does is it uses eval in block fashion. If the call to create the segment fails, the eval returns undef (ie. false) which re-triggers the loop. If the segment creation is successful, the eval returns 1 (ie true) and the loop terminates.
my $shared_memory_segment_created = 0; while (! $shared_memory_segment_created) { $shared_memory_segment_created = eval { tie %events, 'IPC::Shareable', { key => _rand_shm_key(), create => 1, exclusive => 1, protected => _shm_lock(), mode => 0600, destroy => 1 }; 1; }; }
In this next example, I don't use the return value from eval. I don't need to because I later check for the existence of an imported function. What I'm doing here is attempting to require a library and import a specific function. I wrap it in eval in case the module doesn't exist. After the eval block, I check to see if the trace() function loaded or not. If it didn't, I just generate one that does nothing. This allows me to have trace() calls within the code even if the module that contains it isn't available on the system.
eval { require Devel::Trace::Subs; import Devel::Trace::Subs qw(trace); }; if (! defined &trace){ *trace = sub {}; }
The most common way I use eval is like this:
my $thing_ok = eval { do_something(); 1; }; if (! $thing_ok) { # code in eval failed, so return was undef if ($@ =~ /bad parameter/) { # if the die statement died because of a bad param, warn the u +ser # otherwise carry on silently warn $@; } }
Blindly wrapping imported subs with eval is a very bad idea. You'll never know if any failures are generated if you don't check, and that could lead to all manner of undefined behaviour and very difficult to track down bugs. IMHO, eval should only be used when you absolutely expect specific failures, and you have code ready to handle them.
Update: Another very important use case for using eval is to allow cleanup routines to do their job. A die crashes the program, and if any cleanup code is required, it won't run. An example would be the DESTROY method for an object. Sometimes cleanup is very important... resetting GPIO on hardware based systems, cleaning up shared memory segments or temporary files etc.
|
---|