Why can't I call get() on an Optional assigned with ':=' in an if head?

← Control Flow · Ref: Q1963

Because ':=' in a head is a plain assignment: it checks nothing. Only a GUARD pre-flow skips the body when its variable is unset, and only a guard - or a set-check in the condition - proves the variable safe to unwrap in the body:

  <-    declare a new variable, enter only if it is set
  :=?   assign only if unset, enter only if it is set
  :=    assign an existing variable - no check at all

'if name := nextName() then ready' runs its body whenever ready is true, even if nextName() gave an empty Optional, so name.get() there is E08030 (it once compiled and threw 'No such element'). The same holds for 'while name := ...' and 'try name := ...'.

Put the set-check into the condition, before the rest:

  if name := nextName() then name? and ready
    stdout.println(name.get())

A set-check proves the unwrap safe when it is a top-level 'and' operand of the condition, whatever the other operands are - 'name? and ready' and 'name? and (count < limit)' both work, and 'name? and name.get() <> ""' is safe too, because 'and' only evaluates its right side when the left is true. A check under 'or' or 'not' proves nothing; for that, nest an 'if'.

See Q1960 for re-pointing inside a safe access block (E08040), Q1962 for re-pointing and checking in a head and Q84 for Optional's operations.

Example

defines module qa.flow.guard.assignmenthead

  defines function

    nextName()
      <- rtn <- Optional("Steve")

    isReady()
      <- rtn as Boolean: true

  defines program

    AssignmentHeadDemo()
      stdout <- Stdout()

      name <- Optional() of String
      ready <- isReady()
      stdout.println(`before: name set=${name?}`)

      if name := nextName() then name? and ready
        stdout.println("name: " + name.get())
      stdout.println(`after: name set=${name?}`)

      stdout.println("Done")

Common mistakes

E08030 — A ':=' head assigns without checking, so name might be an empty Optional when the body calls name.get(). Check it in the condition - 'then name? and ready' - so the body runs only when name is now set. See ek9 -h E08030 for details.

Incorrect:

      if name := nextName() then ready

Correct:

      if name := nextName() then name? and ready
Other ways to ask this
  • Why is 'if name := nextName() then ready' followed by name.get() error E08030?
  • Does ':=' in an if, while or try head check the value?
  • Which pre-flow operators make an Optional safe to unwrap?
  • How do I make an Optional assigned in an if head safe to unwrap?

Coming from another language?

Java: 'if ((o = next()).isPresent() && ready)' tests explicitly; assigning without the test leaves get() unchecked. Swift: 'if let' tests, a plain assignment before 'if' does not. EK9 keeps every pre-flow in the same head position and lets only the guard forms - or an explicit set-check in the condition - prove an Optional safe.

Keywords: then, optional, safe access, :=?, unwrap, :=, if head, pre-flow, E08030