Why does EK9 say a return is not possible when all branches throw?

← Control Flow · Ref: Q1318

EK9 performs flow analysis on functions and methods that declare a return variable (<- rtn). If every path through the body ends in throw, the declared return can never be assigned or reached, so the compiler raises E07380 ('return not possible, as instructions only result in an Exception'). This usually means a fallible function was written so that BOTH the failure branch and the success branch throw - leaving no normal path that produces a value.

The fix is to keep at least one non-throwing path that assigns the return variable. Throw only for the genuinely exceptional case (typically guarded by a condition), then fall through to assign and return the normal result. A conditional throw is fine; an unconditional throw on every branch is not.

See Q933 for unreachable code after throw (E07370). See Q139 for try/catch versus Result for fallible work.

Example

defines module qa.controlflow.unreachable.return

  defines function

    //FIX: only the failure case throws. The normal path falls through,
    //assigns the return variable, and is reachable - so E07380 is avoided.
    acceptAmount() as pure
      -> amount as Integer
      <-
        rtn as String?
      if amount <= 0
        throw Exception(`bad amount: ${amount}`)
      rtn: `accepted ${amount}`

  defines program

    UnreachableReturnDemo()
      stdout <- Stdout()
      stderr <- Stderr()

      //Normal path: a value is produced and returned.
      stdout.println(acceptAmount(42))

      //Failure path: the conditional throw fires and is caught.
      try
        stdout.println(acceptAmount(-1))
      catch
        -> ex as Exception
        stderr.println(`Caught: ${ex}`)

Common mistakes

E07380 — When both the if and the else branch throw, every path through the function ends in an Exception, so the declared return variable can never be reached or assigned - the function can only ever throw. EK9 raises E07380 because the declared return is then a promise the body cannot keep. Remove the unconditional throw on the success branch (throw only for the failure case) so a normal path remains that assigns the return. See ek9 -h E07380 for details.

Incorrect:

if amount <= 0
  throw Exception(`bad amount: ${amount}`)
else
  throw Exception("unexpected")
rtn: `accepted ${amount}`

Correct:

if amount <= 0
        throw Exception(`bad amount: ${amount}`)
      rtn: `accepted ${amount}`
Other ways to ask this
  • What triggers E07380 in EK9?
  • Why can't my function return a value if every if/else branch throws?
  • How do I fix 'return not possible, as instructions only result in an Exception'?

Coming from another language?

Java: a method whose every branch throws compiles fine - the missing return is simply never required, and dead-code-after-throw is only caught when literally unreachable statements follow. Kotlin/Scala: such a method infers the bottom type 'Nothing' and is accepted silently. Go: returning is optional after a panic, no diagnostic. EK9: because a declared return variable is a contract, a body that can only throw makes that contract unsatisfiable, so the compiler rejects it at compile time (E07380) rather than shipping a function that promises a value it can never deliver.

Keywords: throw, unreachable, all paths, return, E07380, exception, flow, analysis