Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

Re: Exception handling and "trap" vs. TRAPNAL()

Having written most of what follows, I just discovered something that
may make a lot of it moot:

In zsh prior to 4.1.something, an error condition in an inline trap
*WAS* passed through to the calling context.  That it now is not, is
apparently either a broken behavior or an undocumented change, and in
any case no code written for older zsh could have relied upon errors
in traps being inconsequential.

Compare zsh-4.0.6:

schaefer[505] trap 'readonly BAR; BAR= ; print ok' ZERR
schaefer[506] foo() { false; print more }
schaefer[507] foo
foo: read-only variable: BAR
schaefer[508] print $?

Now here's zsh-4.2.5:

schaefer[501] trap 'readonly BAR; BAR= ; print ok' ZERR
schaefer[502] foo() { false; print more }
schaefer[503] foo
foo: read-only variable: BAR
schaefer[504] print $?

This gets even stranger if you compare to bash2.  With an ERR trap,
bash2 behaves like zsh-4.2.5 (except that the ERR trap is reset on
function entry, so you have to put the trap command inside the body).
So maybe this is a standards-compliance thing?

[ASIDE:  The bash2 behavior implies that traps set globally outside
of a function are not supposed to apply within the function context.
E.g., in bash2 a global INT trap does not prevent the function from
being interrupted by a SIGINT; instead it handles the signal in the
context where the trap command was run.  Similarly an ERR trap stops
being tripped during the body of the function, but remains in effect
so that it is tripped if/when the function itself returns nonzero.
This is, almost, the inverse of zsh's "setopt localtraps" behavior.]

Nevertheless ...

On Oct 3,  1:00am, DervishD wrote:
}     I insist: while ZERR may not be the place for throwing exceptions
} (I think it's a perfect place, but that's another matter), signals
} like SIGTERM, SIGINT, etc. or even SIGALRM, are very good candidates
} to do exception handling.

Raúl ... the important point that you're missing is that zsh doesn't
really have or handle exceptions.  Peter's throw/catch functions are
simulations of exception behavior using a mechanism that is far less
powerful than true exception handling.

The zsh "always" syntax could -- and perhaps even should -- have been
implemented equally well as a new syntax for the "eval" builtin, much
like Perl's "eval" can be followed by a curly-bracketed block instead
of a string.  Zsh's TRY_BLOCK_ERROR is a crude approximation of Perl's
$@, in that $TRY_BLOCK_ERROR captures only the presence of an error
and not the error message.  (In fact, a very useful extension would
be to add an EVAL_ERROR variable that is 1 if an "eval" stopped with
an error, and 0 if "eval" finished normally, so that an error could be
distinguished from "eval false".)  The important distinction, though,
is that zsh lacks even an approximation of Perl's "die" builtin.

}   Propagating "errflag" may break current code only if that code is
} using an inline trap which "returns" a value and that error value is
} ignored on purpose.

That's not quite correct.  Remember, an inline trap using the "return"
builtin actually causes the surrounding/calling context to return, so
it's imposible to provide a value to the calling context that way;
"return" in an inline trap is roughly analogous to using "break 2" in
a nested loop.  When "return" is not used, the value of $? from the
calling context is always restored when the trap finishes, so it never
makes sense for the calling context to expect to get a value from an
inline trap.

}     Of course, there are other code that may break: inline traps
} which generate syntax errors [...] that
} previously didn't cause any problem except maybe print an error
} message and now will jump into the "always" block. But I don't think
} there is much code out there using "always" blocks that can break.

The trouble is not with code that is using an "always" block, it's with
code that is NOT using an "always" block.  Which would be the majority
of existing code, because "always" is a very new feature.

So propagating errflag may break current code if that code is using an
inline trap, that code does not have an "always" block, and that code
expects to keep going no matter what happens in the trap.

}     I cannot think about any other code that may break due to the
} last change you're proposing, but anyway your first patch seems
} reasonable too: if inline traps cannot throw exceptions, do not let
} function traps do it and document the problem (not about exceptions,
} but about return values and syntax errors).

Here's another way to think about it:

Presently (that is, without either of my patches), a TRAPNAL function
handles error conditions like a function call, whereas an inline trap
handles error conditions like an "eval" statement.  That's defensible,
in a way, because a "trap" command *looks* like an "eval" statement;
it contains a string that is evaluated as commands.  It could even be
explained that way in the docs, so that you would have understood why
your sample script didn't work.

Given all of this plus the bash2 behavior, I'm inclined to add a few
more words to the documentation and apply *neither* of the patches
from workers/21804.  Further, *IF* we were going to choose one of
those patches to apply, I'd say it should be the first one, to make
TRAPNAL ignore errors too.

Here's how to use throw/catch from a TRAPNAL function in the event
that 21804 part 1 is applied:

    TRAPZERR() { eval 'throw DEFAULT'; return 1 }

That is, call throw in the eval to set the variables, and then return
a positive $? to interrupt into the always block.

Messages sorted by: Reverse Date, Date, Thread, Author