How do I re-point an existing variable and run a block only when the new value is set?

← Control Flow · Ref: Q1024

Assign with ':=' and check the new value with '?'. ':=' ALWAYS evaluates the right side and ALWAYS assigns the result, an unset result included; the explicit check then decides whether the body runs. What this protects is the BODY: code inside it never sees an unset value. It does not protect the target: an unset result overwrites whatever the target held before. (EK9 has no '?=' operator - it was removed; the check is always written out.)

IN IF (and WHILE): the check fits in the head

  if data := fetchRecord(id) then data?
    process(data)

fetchRecord is called and its result is assigned to data. If data is set, the body executes; if it is unset, the body is skipped (an else branch runs if there is one). With a further condition, parenthesise it: 'then data? and (data > limit)'.

IN SWITCH (and FOR, TRY, DO): the head has no condition, so write three steps

  value := parseInput(text)
  if value?
    switch value
      case 1 ...

If the result is unset, the entire switch (including default) is skipped.

A SET TARGET IS OVERWRITTEN

If the target already held a value and the expression returns unset, the target is unset afterwards. To keep an existing value when the new one is missing, use ':=?' (assign only if the target is unset) or assign into a NEW variable with a declaration guard ('if fresh <- fetchRecord(id)').

COMPARISON:

':=' always assigns and checks nothing.
':=' then 'x?' always assigns, then enters the body only if the result is set.
':=?' checks the target BEFORE evaluating: it assigns only if the target is unset (lazy, avoids unnecessary work), then enters only if the target is set.

See Q1023 for the operators compared. See Q759 for assigning and then checking. See Q74 for <- declaration guard.

Example

defines module qa.controlflow.repoint.flow

  defines function

    fetchSetValue()
      <- rtn as Integer?
      rtn: 42

    fetchUnsetValue()
      <- rtn as Integer?
      rtn: Integer()

  defines program

    GuardedAssignmentFlowDemo()
      stdout <- Stdout()

      // === RE-POINT AND CHECK IN IF: the body only sees a SET value ===
      existing <- Integer()

      if existing := fetchSetValue() then existing?
        stdout.println("IF re-point with set value: assigned " + $existing)

      // Reset for next demo
      existing: Integer()

      if existing := fetchUnsetValue() then existing?
        stdout.println(`IF re-point body: never printed ${existing}`)
      else
        stdout.println("IF re-point with unset value: body skipped")

      // === ':=' OVERWRITES A SET TARGET with an unset result ===
      held <- fetchSetValue()

      if held := fetchUnsetValue() then held?
        stdout.println(`Held still: ${held}`)
      else
        stdout.println(`Held was 42, now set: ${held?}`)

      // === RE-POINT AND CHECK AROUND A SWITCH: skips the entire switch when unset ===
      switchControl <- Integer()

      switchControl := fetchUnsetValue()
      if switchControl?
        switch switchControl
          case 42
            stdout.println("Switch matched 42")
          default
            stdout.println("Switch hit default")

      if switchControl?
        stdout.println("Switch entered with value")
      else
        stdout.println("Switch skipped entirely (RHS unset)")

      // === RE-POINT AND CHECK AROUND A SWITCH: enters the switch when set ===
      switchValid <- Integer()

      switchValid := fetchSetValue()
      if switchValid?
        switch switchValid
          case 42
            stdout.println("Switch matched 42 from set value")
          default
            stdout.println("Switch hit default from set value")

Common mistakes

E01073 — 'null' does not exist in EK9 - assign, then check the new value with '?'. See ek9 -h E01073 for details.

Incorrect:

      if existing != null

Correct:

      if existing := fetchSetValue() then existing?
Other ways to ask this
  • How do I assign and check isSet in an if or switch head?
  • What happens when I assign an unset value in a control-flow head in EK9?
  • Does ':=' overwrite a variable that is already set with an unset result?
  • How do I safely assign from a function that might return unset?
  • What replaced the removed ?= guard in an if or switch head?

Coming from another language?

Java: 'data = fetch(); if (data != null && data.isValid()) { switch(data) {...} }'. EK9: 'data := fetch()' then 'if data?' around the switch - or, in an if or while head, 'if data := fetch() then data?'. Rust: if let Some(v) = expr — similar concept but no assignment to an existing variable. Go: No equivalent — must check err separately.

Keywords: null-safe, switch, guarded, protect, flow, control, assignment, unset, isset, check, safe, guard, validate, if