NonFatal vs exhaustive pattern match warning

It is always suggested to use NonFatal to handle exceptions.
What is the best way to avoid the pattern matching exhaustive warning when catching only NonFatal exceptions? Is suppressing it by annotation the only way?


1 Like

Could you post an example with the message?

I don’t see a warning with catch clause.

The exhaustiveness warnings have received many tweaks over time, so versions and compiler options matter.

Sorry, I was not clear before. I mean the usage with match statement.

Here is an example:
I am using Scala 2.13.12, however it is the same even with 3.3.1

import scala.util.control._
import scala.concurrent._
import scala.util._
object Test extends App {
  val result = Future.successful(10)
  result.onComplete {
    case Success(res) => println("Success.. ")
    case Failure(NonFatal(ex)) => println("non fatal")

You are not really “catching” anything here. You are registering a callback (of potentially many) to the completion of the Future, and #onComplete() takes a proper handler function, so you need to cover all potential outcomes therein.

(In a try/catch statement, the catch handler can be a partial function, so you won’t get any exhaustiveness warning there - exceptions not matched will just be re-thrown/bubble up.)

You could log fatal exceptions in your handler just as the non-fatal ones (i.e. just drop the NonFatal match), or, perhaps better, just ignore them in a dedicated fallback case: case Failure(_) => ().

However, it looks like (at least in some scenarios) fatal exceptions are not passed to the handler at all, anyway - not sure whether this is intentional (and documented) or a bug, haven’t worked with Futures in ages…

It is not just in Futures, but the same is applicable to try as well, right.
In one of the projects, the -XFatal was not enabled and I was trying to enable it. While checking the warnings, noticed this exhaustive warnings and was trying to think what is the best approach to avoid the warnings.
At the moment, I did the same as you mentioned; i.e. added a new case with Failure(_) and just logged. But was wondering what is the best approach.

It’s desirable to be explicit in code about implicit assumptions.

scala> def g(e: Throwable) = e match { case _: RuntimeException => 1 }
def g(e: Throwable): Int

scala> :replay -Xlint:strict-unsealed-patmat
replay> def g(e: Throwable) = e match { case _: RuntimeException => 1 }
        warning: match may not be exhaustive.
        It would fail on the following input: (x: Throwable forSome x not in RuntimeException)
def g(e: Throwable): Int

scala> def g(e: Throwable) = (e: @unchecked) match { case _: RuntimeException => 1 }
def g(e: Throwable): Int

An extractor such as NonFatal does not ordinarily “cover all cases”, so for your example, it’s usual to silence the correct warning by adding @unchecked (which makes it clear that you know what you’re doing) or just a catch-all that logs, as you wrote.

There is a discussion in Scala 3 that “NonFatal considered evil” (not a direct quote). The ticket is about whether control-flow exceptions are nonfatal.

The radical suggestion is that code should always be explicit about what “aborts” (exceptions) it handles.

The natural response is, “But isn’t that burdensome and tedious?” Yes, it is.

The situation is like null handling, which is also tedious without tooling or language support.

NonFatal was conceived as a way for a framework like Akka or Future to decide which exceptions are reported to the exception handler and which caught by the framework and returned to user code as a failure. As noted in the other comment, is your result handler supposed to see any failures that are not NonFatal? Those are reported to the execution context.

I think you should not use @unchecked but instead take the advice of the compiler and handle all cases. NonFatal is not appropriate here.

Normally, your application code would be using a different level of abstraction, such as Either[AppError, AppResult].

1 Like

Future already does the NonFatal stuff for you, so don’t worry, just do: case Failure(ex) =>

1 Like

@som-snytt Thanks for the long description. So, in most cases, I see that it doesn’t matter whether you use NonFatal or not, right?

Thanks @BalmungSan I never noticed that.