Because of Scala‘s pattern matching syntax it is really convenient to catch Throwable
instead of Exception
or any other exception type. Instead of writing:
try { iMayHaveIllegalState.doSomething() } catch { case e: IllegalStateException => e.printStackTrace() }
we can simply write:
try { iMayHaveIllegalState.doSomething() } catch { case e => e.printStackTrace() }
You might be tempted to use the above code in your programs since it is shorter but if catching Throwable
in Java is evil then catching Throwable
in Scala is ten times more evil.
To illustrate this we will use a small example. Lets say that we want to write a method that takes a sequence of strings and tries to find if it contains a string that represents an even number. Furthermore if any of the strings cannot get converted into an integer then we assume that the input is invalid and return false
.
A possible implementation is the following:
def containsEven(nums: String*): Boolean = { try { for (i <- nums) { if (i.toInt % 2 == 0) return true } } catch { case e => () } false }
From the above implementation we would expect that containsEven("1", "3")
should return false
and that containsEven("2", "3")
should return true
. Unfortunately this is not the case and regardless of the input our method always returns false
. This is because in the catch
block we used a pattern that catches Throwable
with the expression case e => ...
instead of the longer but more correct pattern case e: NumberFormatException => ...
that only catches NumberFormatException
. To understand why this is the cause of the bug we need to understand how Scala implements non-local return from closures.
The for
loop in the above code is actually a method call to the the foreach
method of the nums
object (that is an instance of scala.Seq
) passing the body of the for
loop as a closure in the foreach
method. The code inside the for
loop is actually the apply(..)
method of the closure (a scala.Function1
in our case). Since that code belongs in another method normally a return
statement present in that code would signal the return of that method.
This is not the case for Scala’s closures and when a return
statement is present in a closure, the closure instead of returning at the point of invocation it also triggers the return of the enclosing method. This behavior is called non-local return and it is essential for creating control-flow abstractions using closures.
Because the JVM does not have native support for non-local return Scala implements it by throwing a Throwable
. When a closure contains a return
statement then that closure when executed instead of returning normally it throws a Throwable
of type scala.runtime.NonLocalReturnControl
that contains the returned value of the closure. The NonLocalReturnControl
is then caught at the enclosing method and closure’s returned value is returned from the enclosing method. This is why when we try to define a closure that contains a return
statement outside of a method the compiler will complain with an error because it cannot find where to generate the catch block for the NonLocalReturnControl
thrown from the closure.
With the knowledge of how non-local return is implemented in Scala, we can now see that in the containsEven
method the NonLocalReturnControl
that gets thrown from the return true
statement in the for
loop gets caught by our case e => ()
pattern and gets ignored. So regardless of the input containsEven
will always return false
. To fix this we must simply catch NumberFormatException
and not Throwable
.
def containsEven(nums: String*): Boolean = { try { for (i <- nums) { if (i.toInt % 2 == 0) return true } } catch { case e: NumberFormatException => () } false }
Catching Throwable
can be a source of frequent bugs because even with the knowledge of how non-local return is implemented it is very likely that you will miss a closure with non-local return and introduce a bug. Furthermore Scala implements and other control structures by throwing Throwable
like break and continuations making it even harder.
In the rare case when you absolutely need to catch Throwable
the correct way to do it is the following:
import scala.util.control.ControlThrowable try { codeThatMayThrowThrowable() } catch { case e: ControlThrowable => throw e case e => handleThrowable(e) }
In the above code we re-throw a Throwable
if it is used for control-flow and handle all others. The scala.util.control.ControlThrowable
is the Throwable
that is extended by all throwables that are used for control flow in Scala.
Thank you.
Wrote about the same. I think that `return` from within closures should be avoided, not really catching `Throwable`s.
It is not a good idea to catch Throwable because java.lang.Error extends Throwable and a lot of Error classes (like ThreadDeath) almost always should never get caught. When you want to catch a Throwable or Error it is far better to catch only the specific type.
I agree with you that non-local returns should be avoided but I wouldn’t want to completely eliminate them from my code.
BTW I really like Circumflex, keep up the good work!
The article that @inca mentioned can be found at http://incarnate.ru/post/1198942448/scala-runtime-nonlocalreturncontrol