3

In Scala, a typical use of pattern matching for exception handling (at least per sources like this and this) looks like the following:

Try(...) match {
  case Success(_) => println("success")
  case Failure(exc) => println(s"caught: $exc")
}

However, that pattern also catches any non-Exception Throwables thrown in the try block:

Try(throw new AssertionError("assertion error")) match {
  case Success(_) => println("success")
  case Failure(exc) => println(s"caught: $exc")
}

caught: java.lang.AssertionError: assertion error

In Java, at least, catching Throwable without a compelling reason to do so is generally considered to be an anti-pattern. (This source offers the same counsel for Scala.)

One option to avoid silently catching Throwable is to catch it and re-throw it:

Try(throw new AssertionError("assertion error")) match {
  case Success(_) => println("success")
  case Failure(exc : Exception) => println(s"caught: $exc")
  case Failure(th) => throw th
}

It seems odd, though, that an extra re-throw is necessary to avoid catching non-Exception Throwables (usually considered unrecoverable), and that letting such Throwables escape, implicit in Java-style try/catch syntax, must be implemented explicitly.

Is there a more succinct / idiomatic syntax for using pattern-matching for exception-handling in Scala, while avoiding inadvertently catching Throwable?

sumitsu
  • 1,481
  • 1
  • 16
  • 33
  • 5
    FWIW, `Try` only catches [NonFatal](https://www.scala-lang.org/api/2.12.3/scala/util/control/NonFatal$.html) throwables. – hoyland Mar 27 '18 at 23:30
  • @hoyland Interesting point; if the answer to my original question is "no" (no such idiomatic syntax exists), maybe that explains why: the designers didn't consider it important to avoid catching `Throwable`, because they had already differentiated fatal vs. non-fatal `Throwable`s via the `NonFatal` extractor. (Though it seems unfortunate that the set of "fatal" `Throwable`s is non-extensible, in that case.) – sumitsu Mar 28 '18 at 15:06

1 Answers1

9

Because the recover() method takes a PartialFunction, it can be used to filter for the type of Throwable you want to handle. After that, you can let get unwrap the Try and retrieve a value if it was either a Success or a handled Failure, or it will re-throw if it's still a Failure, i.e. not handled by recover().

val result = Try{
               //code block here
             }.recover{
               case exc:Exception => ... //should return same type as Success
             }.get //unwrap the Try, will throw if Throwable was not an Exception
jwvh
  • 50,871
  • 7
  • 38
  • 64
  • Interesting approach, but it would seem to compel the use of another frowned-upon practice, calling `Option.get` (https://alvinalexander.com/scala/best-practice-option-some-none-pattern-scala-idioms#don-t-use-the-get-method-with-option). Is there a way to achieve the same effect without calling `get`? `getOrElse(throw ...)` appears to lose the original `Throwable`. – sumitsu Mar 28 '18 at 14:58
  • 1
    The reason `get` is discouraged is because it will throw if you try to `get` the wrong thing (`Option.None` or an unhandled `Try.Failure`), _but that's what you asked for_. The `get` will do the re-throw for you so you don't have to. – jwvh Mar 28 '18 at 16:45