How do guard expressions work in EK9 if statements?

← Control Flow · Ref: Q971

Guard expressions combine variable handling with isSet checking in a single statement. The block only executes if the value is SET.

THREE GUARD FORMS:

1. DECLARATION GUARD (<-):

  if name <- getName()
    stdout.println(name)   declares name, only runs if SET

2. ASSIGNMENT GUARD (:=):

  selectedItem <- String()
  if selectedItem := findItem("key")
    stdout.println(selectedItem)   assigns to existing var, only runs if SET

3. GUARDED ASSIGNMENT (:=?):

  config <- loadDefaults()
  if config :=? loadOverrides()
    stdout.println(config)   only assigns if loadOverrides() returned SET

The := guard always assigns, then checks isSet. The :=? guard only assigns when the right side is SET — the existing value is preserved if the right side is unset.

WITH ADDITIONAL CONDITION:

  if name <- getName() then name.length() > 3
    stdout.println(name)   runs if SET AND length > 3

All three guard forms work IDENTICALLY across all control flow:

  if v <- expr()         declaration guard
  switch v := expr()     assignment guard
  while v :=? expr()     guarded assignment
  try v <- expr()        declaration guard in try
  for v <- iterator      declaration guard in for

This eliminates 90-95% of null pointer exceptions through compile-time enforcement.

Bridge: Like Go's 'if err := doSomething(); err != nil'. Like Rust's 'if let Some(v) = expr()'. Like Swift's 'if let v = optionalExpr'.

See Q74 for more guard examples. See Q75 for guard switch.

Example

defines module qa.controlflow.guardif

  defines function

    <?-
      Returns a greeting if the name has content, or unset if empty.
    -?>
    findGreeting() as pure
      -> personName as String
      <- rtn as String: String()

      if personName?
        rtn: "Hello, " + personName

  defines program

    GuardIfDemo()
      stdout <- Stdout()

      // BASIC GUARD: only enters if findGreeting returns SET
      if greeting <- findGreeting("Steve")
        stdout.println(greeting)

      // GUARD WITH UNSET: block is skipped
      if greeting <- findGreeting(String())
        stdout.println("This won't print")
      else
        stdout.println("Greeting was unset - skipped")

      // GUARD WITH CONDITION: SET and passes additional test
      minLength <- 3
      if greeting <- findGreeting("Alice") then greeting.length() > minLength
        stdout.println(`Long greeting: ${greeting}`)

      // GUARD IN SWITCH: declares and checks in one step
      switch greet <- findGreeting("Charlie") with greet
        case == "Hello, Charlie"
          stdout.println("Found Charlie's greeting")
        default
          stdout.println(`Other greeting: ${greet}`)

Common mistakes

E01073 — 'null' does not exist in EK9 — use tri-state semantics (unset/set) with guard expressions such as 'if name <- expr()' instead of comparing against null. See ek9 -h E01073 for details.

Incorrect:

greeting.length() > minLength and greeting <> null

Correct:

greeting.length() > minLength
Other ways to ask this
  • What is a guard expression in EK9?
  • How does if v <- expr() work in EK9?
  • How do I combine declaration with null checking in EK9?
  • What does the <- guard do inside an if statement?

Coming from another language?

Go: if err := f(); err != nil { } — same concept. Rust: if let Some(v) = expr { } — same concept. Swift: if let v = optional { } — same concept. Java: requires separate null check. EK9 guards are universal across all control flow.

Keywords: guard, null safety, isSet check, control flow, declaration guard