Comment by friendzis
6 days ago
I would not call this snippet particularly "cursed". There is no "aktshchually this happens, therefore this is activated" hidden magic going on. The try-catch-finally construct is doing exactly what it is designed and documented to do: finally block is executed regardless of the outcome within try. The purpose of finally block is to fire regardless of exceptionality in control flow.
Surprising at first? Maybe. Cursed? Wouldn't say so. It is merely unconventional use of the construct.
It messes with the semantics of "return" statement. The conventional intuition is that right after a "return" statement completes, the current method's invocation ends and the control returns to the caller. Unfortunately, the actual semantics has to be that is "attempts to return control":
This, I believe, is the only way for "return E", after the evaluation of E completes normally, to not return the E's value to the caller. Thanks for a needless corner-case complication, I guess.
> It messes with the semantics of "return" statement.
Yes, that's what exceptions and their handling handling does - it messes with control flow.
> The conventional intuition is that right after a "return" statement completes, the current method's invocation ends and the control returns to the caller.
It is part of similar conventional thinking that "a method call must return back to the caller on termination", which is not even applicable to Java and other languages with conventional exceptions.
> Thanks for a needless corner-case complication, I guess.
But what is the alternative otherwise? Not execute finally block if try block exits normally? Execute finally block, but ignore control flow statements? What if code in finally block throws? I maintain that execution of finally block, including control flow statements within, normally is the only sane behavior.
> It is part of similar conventional thinking that "a method call must return back to the caller on termination".
No, it's a part of a conventional thinking that "if a return statement executes without any exceptions thrown in the process, then the control will return from the current invocation". It's an extension of a conventional thinking "if an e.g. assignment statement executes without any exceptions thrown in the process, the control will go to the next statement". Really helps with reasoning about the program behaviour without spending an undue amount of brainpower.
> But what is the alternative otherwise?
Disallow "return", "break", "continue", and "goto" statements inside "finally" blocks, like C# does. Nobody sane misses this functionality anyway.
finally stuff is executed prior to the 'return'; effectively it's placed before the 'return' statement. So it's quite obvious.
No, it's not executed before the return, it's executed in the middle of it, so to speak:
will print
If "finally" block was executed before the "return" in the "try", the call to f() would not have been made.
5 replies →
Sufficiently unexpected (surprising) is cursed. If anything, that’s the way most people use that term for programming language constructs (weird coercions in JavaScript, conflicting naming conventions in PHP, overflow checks that get erased by the compiler due to UB in c/c++, etc.)
> Sufficiently unexpected (surprising) is cursed.
I think there three distinct classes of surprising that fall under this umbrella: 1. a behavior overrides another behavior due to some precedence rules 2. behavior of a construct in language x is different than behavior of similar construct in most mainstream languages 3. who in their right mind would design the thing to behave like this.
Examples could be: string "arithmetic" in most dynamically typed languages, PHP's ternary operator order of precedence and Python's handling of default function arguments. In all of these cases when you sit down, think about it and ask yourself "what's the alternative?", there's always a very reasonable answer to it, therefore, I think it is reasonable to call these behaviors cursed.
In this case the surprise factor comes in, because we are used to equating finally block with cleanup and I concur that many would trip on this the first time. But if you gave this exercise some time and asked "what should happen if finally block contains control flow statements?" the reasonable answer should be "take precedence", because the behavior would be surprising/cursed otherwise.
That's my reasoning.
> In this case the surprise factor comes in, because we are used to equating finally block with cleanup and I concur that many would trip on this the first time. But if you gave this exercise some time and asked "what should happen if finally block contains control flow statements?" the reasonable answer should be "take precedence", because the behavior would be surprising/cursed otherwise.
Wouldn't the reasonable behaviour be "throw a compiler error"?
3 replies →
> But if you gave this exercise some time and asked "what should happen...
This applies to any cursed JavaScript code too (see https://jswtf.com). The increased cognitive load required to figure out unintuitive/counterintuitive code is what makes it cursed.
It's been many, many moons since I touched Java but I would have expected this to run the finally { } clause and then return from the function (similar to how in C++, objects on the stack will run their destructors after a return call before the function ends. I certainly wouldn't expect it to cancel the return.