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 return
ing from the closest lexically enclosing method, i.e. in
def returnFromNestedFunction: Boolean =
someCollection.foreach(x => if x % 2 == 0 then return true)
false
both return
s 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 return
ing 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:
- 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 areturn
(which is practically free), it actually has the performance of an exception (which is very slow). - You can accidentally break the code by having a catch-all exception handler which unintentionally catches the compiler-generated exception.
For this reason, return
ing 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 return
ing 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
}