Saturday, December 13, 2014

Don't ever leave Try::Tiny behind

Hit a bug at work a few days ago; it looked as if Try::Tiny was not doing its job of catching an exception, letting it pass right through instead.  The exception was thrown several layers below the try, which itself was also several layers deep, with lots of evals in-between.  It took me quite a while to prune away all the unrelated bits, until the problematic part of the code was pared down to its simplest expression:
try { die } catch { warn };
Huh?  This piece of code should catch the die and emit a warning instead, but here it was, dying on me.  What was going on?

Actually, there's nothing wrong with that code, and it will do exactly what it is meant to do.  Unless you forget the use Try::Tiny.  Then all hell breaks loose.

In most cases, failing to use a module will typically result in a "Undefined subroutine" or "Can't locate object method" error message that will point to your mistake in an obvious manner.  But in this case, the manifestation can be quite puzzling.

At first, I figured that maybe perl was parsing the first block (i.e. the first argument of the try subroutine) as an anonymous hash; since the try prototype hasn't been declared yet, there's no way to know that this argument is actually a block.  The die will then be executed during the evaluation of the try arguments.

Close, but no cigar.  Turns out that perl is indeed parsing it as a block -- just not in the context we expect:
$ perl -MO=Deparse -e 'try { die } catch { warn }'
do {
    die
}->try(do {
    warn
}->catch);
Here's the indirect object syntax rearing it ugly head; Perl assumes that try is actually a method to be called on whatever class/object the following block will return.  Yuck.  (Notice that the same also applies to catch.)

(Maybe this will finally push me to break the habit of using that syntax for class methods.  Yeah, I know it's unhealthy, but I can't resist...)