Returning a value: there is no return

EK9 has no return statement. A function or method declares its result with '<-', assigns it, and the result is whatever it holds when the body ends. The compiler proves every path leaves it with a value.

Why

EK9 has no 'return'. It is not in the grammar, so there is no warning to ignore and no linter to switch off: writing one is E01072, and the message says what to do instead.

The reason is that an early exit is where function bugs live. A path that forgets to return anything, cleanup that is skipped because the function left early, code after a return that can never run, and the famous duplicated 'goto fail' that skipped the signature verification of a server's key exchange: each is an exit that the reader did not see coming. A function with one way out has none of them.

So a function names its result. 'rtn as String: "unknown"' declares the result and gives it a value, and that value is the answer unless a later statement replaces it. Or declare it with no value, 'rtn as String?', and the compiler proves that every path assigns it before the body ends. A path that does not is E08050, reported on the declaration, which is where the fix goes.

What used to need an early return has an answer that cannot be misread. A guard ('if name <- lookup(code)') runs its body only when the value is set, so the initial value stands otherwise. A 'switch' or a stream produces the value itself. 'filter' skips what 'continue' would skip, and 'head' stops where 'break' would stop.

That is the pattern throughout EK9's control flow: take away the construct that hides the bug, and give the legitimate use a form the compiler can check. The flow control page (linked under Related) makes the same argument for 'break' and 'continue', with the evidence.

The correct form

#!ek9
defines module site.functions.noreturn

  defines constant
    noon <- 12
    evening <- 18

  defines function

    //The initial value is the answer unless a later statement replaces it.
    greeting() as pure
      -> hour as Integer
      <- rtn as String: "Good evening"

      if hour < noon
        rtn: "Good morning"
      else if hour < evening
        rtn: "Good afternoon"

    //No initial value: the compiler proves that every path gives it one.
    sign() as pure
      -> amount as Integer
      <- rtn as String?

      if amount < 0
        rtn: "negative"
      else if amount > 0
        rtn: "positive"
      else
        rtn: "zero"

    //A switch returns a value through its own '<-' variable.
    dayKind() as pure
      -> day as String
      <- rtn as String: switch day
        <- kind as String?
        case "Saturday", "Sunday"
          kind: "weekend"
        default
          kind: "weekday"

    lookup() as pure
      -> code as String
      <- rtn as String?

      if code == "GB"
        rtn: "United Kingdom"
      else
        rtn: String()

    //A guard replaces the early return: the body runs only when lookup gives a set value,
    //otherwise the initial value stands.
    countryName() as pure
      -> code as String
      <- rtn as String: "unknown"

      if name <- lookup(code)
        rtn: name

    isPositive() as pure
      -> amount as Integer
      <- rtn as Boolean: amount > 0

    //A stream replaces a loop with 'continue' or 'break': 'filter' skips items ('head n' would stop
    //early), and '>' adds each Integer into the returning variable.
    sumOfPositives() as pure
      -> amounts as List of Integer
      <- rtn as Integer: 0

      cat amounts | filter by isPositive > rtn

  defines program

    NoReturn()
      stdout <- Stdout()

      stdout.println(greeting(9))
      stdout.println(greeting(20))
      stdout.println(sign(-4))
      stdout.println(sign(0))
      stdout.println(dayKind("Sunday"))
      stdout.println(dayKind("Monday"))
      stdout.println(countryName("GB"))
      stdout.println(countryName("XX"))
      stdout.println($ sumOfPositives([-3, 0, 7, 2]))

Output

Good morning
Good evening
negative
zero
weekend
weekday
United Kingdom
unknown
9

Wrong forms

Each of these is written by people and by models, and each fails to compile - the error is what the compiler tells you.

Writing 'return'

There is no 'return' statement: declare the result with '<-' and assign it; the function ends at the end of its body.

Fails with E01072: 'return' does not exist in EK9 - declare return values with '<-' and compiler ensures all paths initialize

#!ek9
defines module bad.site.functions.noreturn.keyword

  defines function

    sign() as pure
      -> amount as Integer
      <- rtn as String?

      if amount < 0
        return "negative"
      return "not negative"

A returning variable a path never sets

A returning variable declared with no initial value must be given one on EVERY path; the 'zero' path here gives it none. Add the missing 'else', or give it an initial value.

Fails with E08050: return value is not always initialised

#!ek9
defines module bad.site.functions.noreturn.missedpath

  defines function

    sign() as pure
      -> amount as Integer
      <- rtn as String?

      if amount < 0
        rtn: "negative"
      else if amount > 0
        rtn: "positive"