How does try/catch/finally work in EK9?

← Error Handling and Exceptions · Ref: Q135

The finally block in EK9 guarantees cleanup code runs once the try statement is entered, whether the try block succeeds or an exception is thrown and caught.

FULL PATTERN

The complete try/catch/finally structure:

  try
    riskyOperation()
  catch
    -> ex as Exception
    handleError(ex)
  finally
    cleanup()

The finally block always runs after try and catch complete.

FINALLY ALWAYS RUNS ONCE THE TRY IS ENTERED

Finally executes in both of these scenarios:
1. Try completes normally (no exception)
2. Exception thrown and caught
This guarantee makes finally ideal for cleanup operations.

AN UNSET GUARD SKIPS EVERYTHING

A try with a guard (try value <- getValue()) is only entered when the guard is set. When the guard is unset the whole statement is skipped: the try body, the catch AND the finally. Finally guarantees cleanup for a statement that was entered, not for one that never started.

TRY/FINALLY WITHOUT CATCH

You can use try/finally without a catch block:

  try
    doWork()
  finally
    cleanup()

If an exception occurs, it propagates after finally runs.

CLEANUP PATTERNS

Common uses for finally:
- Logging completion status
- Resetting state
- Releasing resources (though try-with-resources is preferred)

NESTED TRY BLOCKS

Try blocks can be nested. Each has its own catch and finally:

  try
    try
      innerOperation()
    catch
      -> ex as Exception
      handleInner(ex)
    finally
      innerCleanup()
  finally
    outerCleanup()

See Q134 for basic try/catch. See Q137 for try-with-resources (automatic cleanup). See Q78 for guard variables in try blocks.

Example

defines module qa.errorhandling.trycatchfinally

  defines function

    riskyOperation()
      -> shouldFail as Boolean
      <- rtn <- String()

      if shouldFail
        ex <- Exception("Operation failed")
        throw ex
      rtn: "success"

  defines program

    TryCatchFinallyDemo()
      stdout <- Stdout()

      // === FULL PATTERN: try/catch/finally ===

      stdout.println("=== Normal execution ===")
      try
        result <- riskyOperation(false)
        stdout.println("Try: " + result)
      catch
        -> ex as Exception
        stdout.println("Catch: " + $ex)
      finally
        stdout.println("Finally: always runs")

      // === EXCEPTION PATH ===

      stdout.println("=== Exception path ===")
      try
        result <- riskyOperation(true)
        stdout.println("Try: " + result)
      catch
        -> ex as Exception
        stdout.println("Catch: " + ex.reason())
      finally
        stdout.println("Finally: still runs after catch")

      // === TRY/FINALLY WITHOUT CATCH ===

      stdout.println("=== Try/finally without catch ===")
      try
        stdout.println("Try: doing work")
      finally
        stdout.println("Finally: cleanup without catch")

      // === NESTED TRY BLOCKS ===

      stdout.println("=== Nested try blocks ===")
      try
        try
          result <- riskyOperation(true)
          stdout.println("Inner try: " + result)
        catch
          -> ex as Exception
          stdout.println("Inner catch: " + ex.reason())
        finally
          stdout.println("Inner finally")
      finally
        stdout.println("Outer finally")

Common mistakes

E07850 — Catch blocks accept only a single exception parameter. EK9 does not support multi-catch syntax like Java. E07850 — only a single Exception is supported. Use the Exception base type and dispatch inside the catch body. See ek9 -h E07850 for details.

Incorrect:

catch
        ->
          ex1 as Exception
          ex2 as Exception
        stdout.println("Catch: failed")

Correct:

catch
        -> ex as Exception
        stdout.println("Catch: " + $ex)

E04030 — The catch parameter must be an Exception type or subtype. Using Integer triggers E04030 — type must be of Exception type. See ek9 -h E04030 for details.

Incorrect:

-> ex as Integer
        stdout.println("Catch: " + $ex)

Correct:

-> ex as Exception
        stdout.println("Catch: " + $ex)

E50060 — EK9 Exception has no getMessage() method. Use reason() to get the exception message. See ek9 -h E50060 for details.

Incorrect:

stdout.println("Catch: " + ex.getMessage())

Correct:

stdout.println("Catch: " + ex.reason())

E04030 — Only Exception types can be thrown in EK9. Throwing a String triggers E04030 — type must be of Exception type. Wrap the message in an Exception constructor. See ek9 -h E04030 for details.

Incorrect:

ex <- "Operation failed"
        throw ex

Correct:

ex <- Exception("Operation failed")
        throw ex

E50060 — EK9 Exception has no getMessage() method. Use reason() for the exception message. See ek9 -h E50060 for details.

Incorrect:

stdout.println("Inner catch: " + ex.getMessage())

Correct:

stdout.println("Inner catch: " + ex.reason())

E50060 — EK9 has no toString() method. Use string concatenation or interpolation for string conversion. See ek9 -h E50060 for details.

Incorrect:

result <- riskyOperation(false)
        stdout.println(result.toString())

Correct:

result <- riskyOperation(false)
        stdout.println("Try: " + result)
Other ways to ask this
  • What does finally do in EK9?
  • How do I guarantee cleanup in EK9?
  • Does EK9 have try-finally?

Coming from another language?

Java: try { } catch { } finally { } with identical guarantee. Python: try: ... except: ... finally: ... same semantics. Rust: no try/finally, uses RAII (Drop trait) for cleanup. Go: defer statement provides cleanup guarantee. Kotlin: try/catch/finally like Java. Swift: defer keyword for cleanup (runs when scope exits, same purpose as finally), no try/finally syntax. EK9: try/catch/finally with the same guarantee once the try is entered; with a guard variable, an unset guard skips the whole statement, finally included.

Keywords: always, finally, resource, nested, swift, defer, pattern, handle, cleanup, catch, error, exception, guarantee, try