Comment by marginalia_nu
6 days ago
On the subject
void foo() {
for (;;) {
try { return; }
finally { continue; }
}
}
is my favorite cursed Java exceptions construct.
6 days ago
On the subject
void foo() {
for (;;) {
try { return; }
finally { continue; }
}
}
is my favorite cursed Java exceptions construct.
To anyone wondering, I believe it's cursed because the finally continue blocks hijacks the try return, so the for loop never returns
So the function returns, and then during its tidyup, the 'continue' basically comefrom()s the VM back into the loop? That is, indeed, cursed.
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.
18 replies →
see, if you only had GOTO's, this would be obvious what is going on!
Python has the same construct but is removing it, starting with a warning in version 3.14: https://peps.python.org/pep-0765/
There was a recent talk at PYCON UK about it, by one of the authors of the PEP in question: <https://www.youtube.com/watch?v=vrVXgeD2fts>
That was a great talk. I was somewhat skeptical of this change, but she really looked at it from every angle and makes a strong argument for the benefits of disallowing the construct. Also, I didn't know that (as mentioned in the talk) C# already disallows it.
Interesting .. from the post above:
> The projects examined contained a total of 120,964,221 lines of Python code, and among them the script found 203 instances of control flow instructions in a finally block. Most were return, a handful were break, and none were continue.
I don't really write a lot of Python, but I do write a lot of Java, and `continue` is the main control flow statement that makes sense to me within a finally block.
I think it makes sense when implementing a generic transaction loop, something along the lines of:
In these cases "swallowing" the exception is often intentional, since the exception could be due to some logic failing as a result of inconsistent reads, so the transaction should be retried.
The alternative ways of writing this seem more awkward to me. Either you need to store the result (returned value or thrown exception) in one or two variables, or you need to duplicate the condition and the `continue;` behaviour. Having the retry logic within the `finally` block seems like the best way of denoting the intention to me, since the intention is to swallow the result, whether that was a return or a throw.
If there are particular exceptions that should not be retried, these would need to be caught/rethrown and a boolean set to disable the condition in the `finally` block, though to me this still seems easier to reason about than the alternatives.
> Having the retry logic within the `finally` block seems like the best way of denoting the intention to me, since the intention is to swallow the result, whether that was a return or a throw.
Except that is not the documented intent of the `finally` construct:
Using `finally` for implementing retry logic can be done, as you have illustrated, but that does not mean it is "the best way of denoting the intention." One could argue this is a construct specific to Java (the language) and does not make sense outside of this particular language-specific idiom.
Conceptually, "retries" are not "cleanup code."
0 - https://docs.oracle.com/javase/tutorial/essential/exceptions...
6 replies →
Doesn't that code ignore errors even if it runs out of retries? Don't you want to log every Exception that happens, even if the transaction will be retried?
This code is totally rotten.
1 reply →
> if (!tx.commit())
https://docs.oracle.com/javase/8/docs/api/java/sql/Connectio...:
⇒ this code I won’t even compile for the java.sql.Transaction” class that is part of the Java platform.
(I think having commit throw on error is fairly common. Examples: C# (https://learn.microsoft.com/en-us/dotnet/api/system.data.sql...), Python (https://docs.python.org/3/library/sqlite3.html#sqlite3.Conne...))
1 reply →
This code is wrong. You don't want to commit a transaction if an exception is thrown during the transaction.
1 reply →
Just tested that in C# and it seems they made the smart decision to not allow shenanigans like that in a finally block:
CS0157 Control cannot leave the body of a finally clause
What about throwing an exception from the finally clause?
This loops, if that's what you're asking:
Yes that is the one exception (heh) to the rule unfortunately. You can throw from anywhere so it must support unwinding from any point. So if you were really intent on abusing the finally you could wrap the try-finally in a try-catch and then throw from the finally and put your continue in the catch.
The finally behave slightly different in CIL. You have protected regions and finally/fault/catch/filters handlers attached. So in order to support continue inside finally you should introduce some state machine , which is complication and generally against Roslyn design limitation.
ziml77's point is about the behaviour of the C# language. You seem to be talking about implementation concerns. They're not relevant.
That's not just Java and there is nothing really cursed about it: throwing in a finally block is the most common example. Jump statements are no different, you can't just ignore them when they override the return or throw statements.
It is just Java as far as I can tell. Other languages with a finally don't allow for explicitly exiting the finally block.
And JavaScript .. And Python (though as sibling posts have mentioned it looks like they're intending to make a breaking change to remove it).
EDIT: actually, the PEP points out that they intend for it to only be a warning in CPython, to avoid the breaking change
Notably, C++ and similar languages don't support lexical `finally` at all, instead relying on destructors, which are a function and obviously cannot affect the control flow of their caller ...
except by throwing exceptions, which is a different problem that there's no "good" solution to (during unwinding, that is).
2 replies →
> override the return
How is this not cursed
It is Java as C# disallow this
In JDK 25, you can run this code:
This is exceedingly nasty. Well Done!
try/finally is effectively try/catch(Throwable) with copy all the code of the finally block prior to exiting the method. (Java doesn't have a direct bytecode support for 'finally')
Nothing that cursed.
It compiles to this:
Won't that particular code fail to compile in java because the return is unreachable?
this broke my head. I think I haven't touched Java in a while and kept thinking continue should be in a case/switch so ittook a minute to back out of that alleyway before I even got what was wrong with this.
Isn't this just an endless loop with extra steps?