Executive summary: I no longer want catch blocks to "daisy chain"
after a exception is thrown in a catch block. Thanks to everyone
who has helped me see the light on this.
Peter Scott wrote:
>
> At 01:16 AM 8/16/00 -0600, Tony Olekshy wrote:
> >
> > The proposed omnibus Exceptions RFC uses the following three
> > rules to guide it while unwinding through the clauses of a
> > try statement.
>
> Forgive me for eliding your explanation, but I find it a little
> opaque
Me too, that's why the subject contains the word "Towards".
> Let me advance a model which may be simpler.
>
> 1. When an exception is thrown perl looks for the enclosing
> try block; if there is none then program death ensues.
>
> 2. If there is an enclosing try block perl goes through the
> associated catch blocks in order. If the catch criteria
> succeed (the exception class matches one in a list, or a
> catch expression evaluates to true, or the catch block
> catches everything), the catch block is executed.
>
> If the catch block throws an exception, it becomes the
> 'current' exception (with a link to the previous one),
> otherwise there is no longer a current exception.
>
> 3. Whether or not a catch block was executed, the finally
> block is now executed if there is one. If the finally block
> throws an exception, it becomes the 'current' exception
> (with a link to the previous one if there was one). At
> this point, if there is a current exception, go to step 1.
>
> This seems complete and IMHO easily understood.
Yes, that's much better. Thanks Peter. Here is a slightly
different version which is still slightly more complex, but
which allows me to more clearly illustrate the change I am
proposing.
1. Whenever an exception is thrown it becomes the current
exception (with a link to the previous one if there was one)
and Perl looks for an enclosing try/catch/finally block.
If there is no such enclosing block then program death ensues,
otherwise Perl traps the exception and proceeds as per rule 2.
2. The relevant try block's next associated catch or finally block
is processed according to rules 3 and 4. When there are no
more blocks use rule 5.
3. If the criteria for a catch succeed (an exception was thrown
and either the exception class matches one in a list, or a
catch expression evaluates to true without throwing, or the
catch block catches all exceptions), the catch block is
executed.
If the catch block and its test expression do not throw then
the current exception is cleared. Otherwise, the exception is
trapped and it becomes the 'current' exception (with a link to
the previous one).
Processing continues with rule 2. [But see below --Tony]
4. When a finally block is encountered it is executed.
If the finally block throws an exception it is trapped and it
becomes the 'current' exception (with a link to the previous
one if there was one).
Processing continues with rule 2.
5. After the catch and finally blocks are processed, if there
is a current exception then go to step 1. Otherwise continue
with the statement after the try statement.
My question is: where should processing continue after rule 3?
The version shown above produces the problem I described before:
> > try { } except { } => catch { }
> > except { } => catch { }
> > catch { }
> >
> > The potential problem is that if the first catch throws, then
> > the second except will be testing against the $@ from the catch,
> > not the $@ from the try.
> >
> > Is this a problem?
>
> Yes, I think it breaks the intuitive model.
I do too, so the question is what to do about it. I propose changing
rule 3 to add the new paragraph marked with **:
3. If the criteria for a catch succeed (an exception was thrown
and either the exception class matches one in a list, or a
catch expression evaluates to true without throwing, or the
catch block catches all exceptions), the catch block is
executed.
If the catch block and its test expression do not throw then
the current exception is cleared. Otherwise, the exception is
trapped and it becomes the 'current' exception (with a link to
the previous one).
** If a catch test succeeds (or throws), then whether or not the
** catch block throws, any succeeding catch blocks up to the next
** finally block are skipped.
Processing continues with rule 2.
This probably means that it should be a syntax error to have any
catch clause follow a non-conditional catch, because such a clause
could never be executed.
> throws() outside a try block are caught by the catch
> blocks of the next enclosing try block. See above.
Not quite. Throws in a catch block (not a try block per se) still
have to be trapped in order to get to the finally block, if any.
I think you probably meant that, I'm just making it explicit.
The change I described above would get the effect we want, that is,
that throws in a catch block prevent subsequent catch blocks (up to
the next finally, if any) from being attempted, and after the finally
the throw from the catch is propagated.
To put it another way, catches don't "daisy chain" any more. Once
a catch is triggered, subsequent catches (up to the next finally
block, if any) are ignored, even if the triggered catch threw. I
think that gets rid of much of the complexity we have been trying
to avoid, and we can always use nested trys if we really need to.
> Let's leave $@ out of the discussion for now, since it implies a
> place to store an exception which is unnecessarily global.
I was just using it instead of $_[0] to try to make it clear that
I was talking about the current exception. Perhaps I made it less
clear, eh?
Yours, &c, Tony Olekshy