Is it possible for a Perl routine to force a caller to return?

If I have a Perl module like

package X; 

and an object like

  my $x = X->new (); 

Inside X.pm, I write an error handler for $x called handle_error , and I call it

  sub check_size { if ($x->{size} > 1000) { $x->handle_error (); return; } } 

Is there a way to make handle_error force a return from my call procedure? In other words, in this example, can I do handle_error do return in check_size without writing return there?

+6
perl
source share
5 answers

The only sensible way to drop multiple levels up the call stack is to throw an exception / death.

However, you really need to rethink what you want to do. The programmer (including you, after six months) will expect that when the function call completes, the operator will execute it after execution (unless an exception is thrown). Failure to do so will cause errors caused by handle_error , but it seems to have code called handle_error , which makes them extremely difficult to debug. This is not a good thing.

You also make the assumption that there is absolutely no situation in which continuing after processing the error would be appropriate. A hardcoding assumption like this is an almost reliable guarantee that as soon as you have time to forget it, you will encounter a situation where you need to continue after calling handle_error (and then spend a huge amount of time trying to figure out why the code is after handle_error does not fire).

And then the assumption arises that you always want to skip exactly two levels in the call stack. This is another assumption that will fail as soon as it is hard-coded. Not only will there be cases where the call code should continue, there will also be cases when you need to go three levels up the call stack.

So, just handle_error exit by calling die instead of return and delaying the exception at the appropriate level where execution should continue. You don’t know every place where sub will ever be called, so you cannot predict how many levels it will need back.

In the above code, if an extra line, just saying return bothers you, you can use return $x->handle_error; You can even get rid of the enclosing area and make it return $x->handle_error if $x->{size} > 1000; There - three lines are deleted than one, plus a pair of curly braces and two pairs of parentheses as a free bonus.

Finally, I would also suggest changing the name handle_error to better reflect what it actually does. ( report_error , maybe?) "Error handling" usually means clearing until the error is resolved in order to continue execution. If you want your handle_error prevent continuation of the code that caused it, then it is unlikely that it cleared things to make continuation possible, and again, it will cause unpleasant, hard-to-debug surprises for future programmers using this code.

+11
source share

You can use goto & NAME , your return from the error handler will return to the point where check_size was called.

sub check_size {my $ x = shift; # you do not say where $ x comes in X.pm.
# I assume that he is the initiator.

 if( $x->{size} > 1000 ) { my $sub = $x->can('handle_error'); goto $sub; } 

}

This works because goto &NAME transfers control to the called function without creating a new stack frame.

I use can to get a handle_error reference for $x , so the method will work correctly with subclasses that override handle_error .

This design seems like a bad idea to me.

Perhaps this is a good place to use exceptions:

 use Try::Tiny; my $x = X->new(); try { $x->check_size } catch { $x->handle_error }; 
+4
source share

The answer to your question is extremely complex, and I'm not even going to reveal it, because this is a bad solution to your real problem. What is the real problem? The problem is that you want the error inside a deeply nested subprogram call to cause a bubble on the stack. Exceptions are needed for this.

Here your code is rewritten for exception using croak .

 package X; sub new { my $class = shift; my %args = @_; my $obj = bless \%args, $class; $obj->check_size; return $obj; } my $Max_Size = 1000; sub check_size { my $self = shift; if ($self->{size} > $Max_Size) { croak "size $self->{size} is too large, a maximum of $Max_Size is allowed"; } } 

Then, when the user creates an invalid object ...

 my $obj = X->new( size => 1234 ); 

check_size dies and throws an exception from the stack. If the user does nothing to stop him, they get the error message "size 1234 is too large, a maximum of 1000 is allowed at somefile line 234" . croak that the error message occurs at the point where new called, where the user made the error, and not somewhere deep inside X.pm.

Or they can write new () in eval BLOCK to catch the error.

 my $obj = eval { X->new( size => 1234 ) } or do { ...something if the object isn't created... }; 

If you want to do something else when an error occurs, you can wrap croak in a method call.

 sub error { my $self = shift; my $error = shift; # Leaving log_error unwritten. $self->log_error($error); croak $error; } my $Max_Size = 1000; sub check_size { my $self = shift; if ($self->{size} > $Max_Size) { $self->error("size $self->{size} is too large, a maximum of $Max_Size is allowed"); } } 

An exception from croak will cause the bubble to stack through error , check_size and new .

As daotoad points out, Try :: Tiny is a better exception handler than the direct eval BLOCK .

See If the Perl constructor returns an undef or "invalid" object? for more reasons why exceptions are a good idea.

+2
source share

You can use Continuation :: Escape . This will basically allow you to pass a "return point" to the error handler.

+1
source share

A workaround that works, but not nice to see.

Use the select statement.

If you have the my_func () function that you are calling, and then based on the return value you want to do some error handling, and then return from the caller, you can do:

 ($result = my_func()) == 0 ? return handle_error($result) : 0; 

This is an (ugly) single line line that replaces:

 $result = my_func(); if ($result == 0) { handle_error($result); return; } 

It is convenient if you have many functions for calling and checking the return value.

0
source share

All Articles