void foo() {
for (;;) {
try { return; }
finally { continue; }
}
}
is my favorite cursed Java exceptions construct.Surprising at first? Maybe. Cursed? Wouldn't say so. It is merely unconventional use of the construct.
> 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:
<T> T executeTransaction(Function<Transaction, T> fn) {
for (int tries = 0;; tries++) {
var tx = newTransaction();
try {
return fn.apply(tx);
} finally {
if (!tx.commit()) {
// TODO: potentially log number of tries, maybe include a backoff, maybe fail after a certain number
continue;
}
}
}
}
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.
Except that is not the documented intent of the `finally` construct:
The finally block always executes when the try block exits.
This ensures that the finally block is executed even if an
unexpected exception occurs. But finally is useful for more
than just exception handling — it allows the programmer to
avoid having cleanup code accidentally bypassed by a
return, continue, or break. Putting cleanup code in a
finally block is always a good practice, even when no
exceptions are anticipated.[0]
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...
This code is totally rotten.
https://docs.oracle.com/javase/8/docs/api/java/sql/Connectio...:
void commit()
throws SQLException
⇒ 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...))
CS0157 Control cannot leave the body of a finally clause
while (true)
{
try
{
try { return; }
finally { throw new Exception(); }
}
catch { }
}EDIT: actually, the PEP points out that they intend for it to only be a warning in CPython, to avoid the breaking change
https://www.cs.purdue.edu/homes/hosking/m3/reference/exit.ht...
But there, it's more comprensible because in order to define the interaction of return (and exit) with exceptions, they define return, and exit, to be exceptions. So then it's more obvious why return can be "caught".
except by throwing exceptions, which is a different problem that there's no "good" solution to (during unwinding, that is).
How is this not cursed
$ cat App.java
void main() {
for (;;) {
try { return; }
finally { continue; }
}
}
$ java App.javaNothing that cursed.
It compiles to this:
void foo() {
for (;;) {
try {
continue;
return; }
catch (Throwable t) { continue; }
}
}A minor point:
> monitors are incompatible with coroutines
If by coroutines the author meant virtual threads, then monitors have always been compatible with virtual threads (which have always needed to adhere to the Thread specification). Monitors could, for a short while, degrade the scalability of virtual threads (and in some situations even lead to deadlocks), but that has since been resolved in JDK 24 (https://openjdk.org/jeps/491).
Holding a lock/monitor across a yield is a bad idea for other reasons, so it shouldn't be a big deal in practice.