May 09, 2014

## The Point of No Return

Alright, every time Martin’s Coursera course runs we get people in #scala asking why they get style points taken off for using `return`. So here’s the pro tip:

The `return` keyword is not “optional” or “inferred”; it changes the meaning of your program, and you should never use it.

Let’s look at a little example.

``````// Add two ints, and use this method to sum a list
def add(n:Int, m:Int): Int = n + m
def sum(ns: Int*): Int = ns.foldLeft(0)(add)

scala> sum(33, 42, 99)
res0: Int = 174

// Same, using return
def addR(n:Int, m:Int): Int = return n + m
def sumR(ns: Int*): Int = ns.foldLeft(0)(addR)

scala> sumR(33, 42, 99)
res1: Int = 174
``````

So far so good. There is no apparent difference between `sum` and `sumR` which may lead you to think that `return` is simply an optional keyword. But let’s refactor a bit by inlining `add` and `addR`.

``````// Inline add and addR
def sum(ns: Int*): Int = ns.foldLeft(0)((n, m) => n + m) // inlined add

scala> sum(33, 42, 99)
res2: Int = 174 // alright

def sumR(ns: Int*): Int = ns.foldLeft(0)((n, m) => return n + m) // inlined addR

scala> sumR(33, 42, 99)
res3: Int = 33 // um.
``````

### What the what?

So, the short version is:

A `return` expression, when evaluated, abandons the current computation and returns to the caller of the method in which `return` appears.

So in the example above, the `return` statement in the anonymous function does not return from the function it appears in; it returns from the method it appears in. Another example:

``````def foo: Int = {
val sumR: List[Int] => Int = _.foldLeft(0)((n, m) => return n + m)
sumR(List(1,2,3)) + sumR(List(4,5,6))
}

scala> foo
res4: Int = 1
``````

### Non-Local Return

When a function value containing a `return` statement is evaluated nonlocally, the computation is abandoned and the result is returned by throwing a `NonLocalReturnControl[A]`. This implementation detail escapes into the wild without much ceremony:

``````def lazily(s: => String): String =
try s catch { case t: Throwable => t.toString }

def foo: String = lazily("foo")
def bar: String = lazily(return "bar")

scala> foo
res5: String = foo

scala> bar
res6: String = scala.runtime.NonLocalReturnControl
``````

To those who say well you should never catch `Throwable` anyway, I say well you shouldn’t be using exceptions for flow control. The `breakable { ... }` nonsense in stdlib uses a similar technique and similarly should not be used.

Another example. What if a `return` expression is captured and not evaluated until after the containing method has returned? Well you now have a time-bomb that will blow up whenever it’s evaluated.

``````scala> def foo: () => Int = () => return () => 1
foo: () => Int

scala> val x = foo
x: () => Int = <function0>

scala> x()
scala.runtime.NonLocalReturnControl
``````

And as an extra bonus `NonLocalReturnControl` extends `NoStackTrace` so you are given no clue about where the bomb was manufactured. Good stuff.

For this reason, if you make a choice to use `return`, you should practice safe returns and use `-Xlint:nonlocal-return`, available with 2.13:

``````scala> def foo: () => Int = () => return () => 1
^
warning: return statement uses an exception to pass control to the caller of the enclosing named method foo
foo: () => Int
``````

However, the safest practice remains abstinence.

### What is the type of a return expression?

In `return a` the returned expression `a` must conform with the return type of the method in which it appears, but the expression `return a` itself also has a type, and from its “abandon the computation” semantics you can probably guess what that type is. If not, here’s a progression for you.

``````def x: Int = { val a: Int = return 2; 1 } // result is 2
``````

Well this typechecks so our guess might be that the type of `return a` is the same as the type of `a`. So let’s test that theory by trying something that shouldn’t work.

``````def x: Int = { val a: String = return 2; 1 }
``````

Hmm, that typechecks too. What’s going on? Whatever the type of `return 2` is, it conforms with both `Int` and `String`. And since both of those classes are final and `Int` is an `AnyVal` you know where this is headed.

``````def x: Int = { val a: Nothing = return 2; 1 }
``````

Right. So, whenever you encounter an expression of type `Nothing` you would do well to turn smartly and head the other direction. Because `Nothing` is uninhabited (there are no values of that type) you know that the expression has no normal form; when evaluated it must loop forever, exit the VM, or (behind door #3) abruptly pass control elsewhere, which is what’s happening here.

If your reaction is “well logically you’re just invoking the continuation, which we totally do all the time in Scheme so I don’t see the problem” then fine. Cookie for you. The rest of us think it’s insane.

### Return is not referentially transparent.

This kind of goes without saying, but just in case you’re not sure what this means, if I say

``````def foo(n:Int): Int = {
if (n < 100) n else return 100
}
``````

then I should be able to rewrite my program thus, with no change in meaning

``````def foo(n: Int): Int = {
val a = return 100
if (n < 100) n else a
}
``````

which of course doesn’t work. Evaluating a `return` expression is a side-effecting operation.

### But what if I really need it?

You don’t. If you find yourself in a situation where you think you want to return early, you need to re-think the way you have defined your computation. For example:

``````// Add up the numbers in a list, up to 100 max
def max100(ns: List[Int]): Int =
ns.foldLeft(0) { (n, m) =>
if (n + m > 100)
return 100
else
n + m
}
``````

can be rewritten using simple tail recursion:

``````// Add up the numbers in a list, up to 100 max
def max100(ns: List[Int]): Int = {
def go(ns: List[Int], a: Int): Int =
if (a >= 100) 100
else ns match {
case n :: ns => go(ns, n + a)
case Nil     => a
}
go(ns, 0)
}
``````

This is always possible. Eliminating `return` from the Scala language would result in zero programs that could no longer be written. It may take a bit of effort to get into the mindset, but in the end you will find that writing computations that terminate properly is far easier than trying to reason about side-effects manifested as nonlocal flow control.