How do guard variables work in try blocks?

← Control Flow · Ref: Q78

In EK9, guard variables in try blocks combine variable creation with an isSet check before executing the try body. If the guard is unset, the try body is skipped, catch does NOT execute, but finally still runs.

BASIC TRY GUARD

Use the declaration operator (<-) to guard a try block:

  try resource <- acquireResource()
    useResource(resource)
  catch
    -> ex as Exception
    handleError(ex)
  finally
    cleanup()

If acquireResource() returns unset, the try body is skipped. The catch block does NOT fire because no exception occurred. The finally block ALWAYS runs.

GUARD VS EXCEPTION

This is the key distinction:

  Guard unset: Resource unavailable. Try body skipped. No error.
  Exception: Resource acquired but fails during use. Catch handles it.

Guard failures are expected situations (optional resource not available). Exceptions are unexpected errors during processing.

TRY EXPRESSION WITH GUARD

Try can be an expression that returns a value:

  result <- try config <- loadConfig()
    <- rtn as String: "default"
    rtn: config.host
  catch
    -> ex as Exception
    rtn: "error: " + $ex

If the guard is unset, the expression evaluates to the default return value.

FINALLY ALWAYS RUNS

Regardless of whether the guard is set or unset, or whether an exception occurs, the finally block always executes. This is identical to Java's finally guarantee.

See Q74 for guards in if. See Q75 for guards in switch. See Q76 for guards in for loops. See Q77 for guards in while loops. See Q134 for basic try/catch without guards. See Q135 for try/catch/finally. See Q137 for try-with-resources.

Example

defines module qa.flow.guard.tryblock

  defines record

    DbConnection
      url <- String()

      default operator ?

  defines function

    openConnection()
      <- rtn <- DbConnection()
      rtn.url: "jdbc:example:db"

    openFailedConnection()
      <- rtn <- DbConnection()

  defines program

    GuardTryDemo()
      stdout <- Stdout()

      // === BASIC TRY GUARD (resource available) ===

      try conn <- openConnection()
        stdout.println("Connected to: " + conn.url)
      catch
        -> ex as Exception
        stdout.println("Error: " + $ex)
      finally
        stdout.println("Cleanup after connection attempt")

      // === TRY GUARD WITH UNSET (try body skipped) ===

      try conn <- openFailedConnection()
        stdout.println("This should not print")
      catch
        -> ex as Exception
        stdout.println("Catch should not fire either")
      finally
        stdout.println("Finally always runs even when guard is unset")

      // === TRY EXPRESSION WITH GUARD ===

      result <- try conn <- openConnection()
        <- rtn as String: "no connection"
        rtn: "connected to " + conn.url
      catch
        -> ex as Exception
        rtn: "error"

      stdout.println("Result: " + result)

Common mistakes

E01072 — EK9 has no return statement. Try guards combine resource acquisition and isSet checking. If the guard is unset, the try body is skipped safely without needing an early return. See ek9 -h E01072 for details.

Incorrect:

conn <- openConnection()
      if not conn?
        return
      try
        stdout.println("Connected to: " + conn.url)
      catch
        -> ex as Exception
        stdout.println("Error: " + $ex)

Correct:

try conn <- openConnection()
        stdout.println("Connected to: " + conn.url)
      catch
        -> ex as Exception
        stdout.println("Error: " + $ex)

E01072 — EK9 has no return statement. Try expressions use a declared return variable that is assigned in the body and catch branches. See ek9 -h E01072 for details.

Incorrect:

try conn <- openConnection()
        return "connected to " + conn.url

Correct:

result <- try conn <- openConnection()
        <- rtn as String: "no connection"
        rtn: "connected to " + conn.url
Other ways to ask this
  • Can I guard a try block with a variable declaration?
  • How does try with guard work in EK9?
  • What happens if a try guard is unset?
  • How do I use try-catch in EK9?
  • How does exception handling work in EK9?

Coming from another language?

Java: No guard in try. Try-with-resources handles cleanup but cannot skip the try body if resource is unavailable. Must wrap: 'var r = acquire(); if (r != null) { try { use(r); } catch ... }'. Python: No guard in try. 'with' statement handles cleanup but cannot conditionally skip. Must wrap: 'r = acquire(); if r: try: use(r)'. Rust: No guard in try (Rust uses Result/Option instead of try/catch). Pattern matching with '?' operator is different. Go: No try/catch. Defers handle cleanup. Guard concept does not apply. EK9: 'try resource <- acquireResource()' combines resource acquisition, isSet check, and exception handling. Guard failure is silent (no exception), finally always runs.

Keywords: handle, migrate, guard, null-safe, try, finally, acquire, branch, exception, resource, flow, safe, control, isset, catch, error, condition, declaration