Home > Net >  return early in Unit-returning function in Scala
return early in Unit-returning function in Scala

Time:12-29

Imagine I have a function in Scala that returns a Unit-value. The function does a certain test at the beginning and concludes that it could stop, i.e. return, already. Is it safe to just put a return statement (without anything) and leave the function?

CodePudding user response:

Imperative side-effecting code is mostly non-idiomatic in Scala, so you won't find any guidance about exactly how to write imperative side-effecting code in Scala.

It sounds like you are talking about a typical guard clause style like

def procedureWithGuard(): Unit =
  if nothingToDo then return
  doTheExpensiveThing()

procedureWithGuard()

This is perfectly fine imperative style. It is just not perfectly fine Scala style, but that's not because of the early return, but because of the use of side-effects in general.

Note that there is a change in Scala 3: in the past, a return in a nested anonymous function was returning from the closest lexically enclosing method, i.e. in

def returnFromNestedFunction: Boolean =
  someCollection.foreach(x => if x % 2 == 0 then return true)
  false

both returns would return from the method returnFromNestedFunction, even though the first return is actually inside the apply method of some automatically generated instance of Function1[T, Boolean]. This requires the compiler to jump through some hoops since most of the target platforms supported by Scala (JVM, ECMAScript, and in the past .NET) simply do not support returning from one method in a different method, and they also do not support GOTO across methods.

The way this was implemented in Scala was that the inner return was compiled into a throw of a special exception, and at the place where it returns to, the compiler synthesized a corresponding catch. However, this has two problems:

  1. On all supported platforms, throwing and catching an exception is slow, much slower than return. So, while it looks like the code would have the performance of a return (which is practically free), it actually has the performance of an exception (which is very slow).
  2. You can accidentally break the code by having a catch-all exception handler which unintentionally catches the compiler-generated exception.

For this reason, returning from a nested anonymous function is deprecated in Scala 3. Instead, there is a library which makes it easy to use the exception throwing trick explicitly, so that there is no hidden performance cost (the library makes the exception throwing trick easy to use, but it does not hide the fact that an exception is involved) and you can't accidentally catch a hidden exception you don't know about (because the exception isn't hidden in the first place).

import scala.util.control.NonLocalReturns.*

def returnFromNestedFunction = returning {
  someCollection.foreach(x => if x % 2 == 0 then throwReturn(true))
  false
}

Note that it is not always obvious at first glance that you are returning from a nested anonymous function. In particular, for-comprehensions desugar into foreach (if there is no yield), map (if there is a yield), or flatMap (if there are multiple generators) and withFilter (if there is an if). For example, this code is actually the same as above, but there is no obviously visible anonymous function:

import scala.util.control.NonLocalReturns.*

def returnFromNestedFunction = returning {
  for x <- someCollection do
    if x % 2 == 0 then throwReturn(true)
  false
}
  • Related