25

A colleague just showed me this and I was surprised that it compiled at all:

def toUpper(s: Option[String]): String = {
  s.getOrElse(return "default").toUpperCase
  //          ^^^^^^  // a return here in the closure??
}

and this even works:

println(toUpper(Some("text"))) // TEXT
println(toUpper(None))         // default

I thought return from inside a closure was not allowed. Since when has this worked? Are there caveats with such non-local returns?

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Jean-Philippe Pellet
  • 59,296
  • 21
  • 173
  • 234
  • Very confusing, since neither `Some("text").getOrElse(return "default").toUpperCase` nor `None.getOrElse(return "default").toUpperCase` compile. – agilesteel Aug 02 '11 at 17:21
  • 3
    `def foo(): String = Some("text").getOrElse(return "default").toUpperCase` compiles fine; so does `def foo(): String = (None: Option[String]).getOrElse(return "default").toUpperCase`. – Alexey Romanov Aug 02 '11 at 19:11
  • @Alexey Sure, but it does not if you drop the `def foo():String =` part. – agilesteel Aug 02 '11 at 20:14
  • 1
    @agilesteel The error message says there needs to be an enclosing method to return to. – Kipton Barros Aug 02 '11 at 20:34
  • 5
    I don't see anything confusing about that. `return` in Scala always returns from the enclosing method; if you aren't inside a method definition, you can't use `return`. – Alexey Romanov Aug 02 '11 at 20:40
  • @Kipton Barros That makes sense. I just assumed, that since the parameter of `getOrElse` is by name (meaning that a block of code can be passed is), the `return` might refer to this block and not to the enclosing method. But apparently it is not. – agilesteel Aug 02 '11 at 20:57

2 Answers2

26

The semantics are relatively simple: return will throw a NonLocalReturnControl that is caught at the enclosing method, toUpper. It doesn't look like a recent feature; there is no mention of return in the Scala change-log since version 2.0.

Here's the relevant description from the Scala Language Spec, section 6.20:

Returning from a nested anonymous function is implemented by throwing and catching a scala.runtime.NonLocalReturnException. Any exception catches between the point of return and the enclosing methods might see the exception. A key comparison makes sure that these exceptions are only caught by the method instance which is terminated by the return.

If the return expression is itself part of an anonymous function, it is possible that the enclosing instance of f has already returned before the return expression is executed. In that case, the thrown scala.runtime.NonLocalReturnException will not be caught, and will propagate up the call stack.

Here's an example in which the NonLocalReturnControl escapes:

var g: () => Unit = _
def f() { g = () => return }
f() // set g
g() // scala.runtime.NonLocalReturnControl$mcI$sp
Kipton Barros
  • 21,002
  • 4
  • 67
  • 80
6

It's been allowed since forever, more or less. It might look strange there, but there are many places where the reverse would be true. For example:

// excessive use of braces for the sake of making scopes clearer

def findFirst[A](l: List[A])(p: A => Boolean): Option[A] = {
    for (x <- l) {
        if (p(x)) return Some(x)
    }
    None
}
Daniel C. Sobral
  • 295,120
  • 86
  • 501
  • 681