If I have a String variable that might be unset, how can I conditionally assign a default value to it only when it has not been set yet?

← Operators and Expressions · Ref: Q1263

EK9 has a dedicated operator for 'assign if unset': :=? (assign-if-unset). It assigns the right-hand value to the variable ONLY if the variable is currently unset. If the variable already has a value, the assignment is skipped - the existing value is preserved.

THE OPERATOR

  variable :=? defaultValue

WORKED EXAMPLE - SOME PATHS SET IT, ONE DOES NOT

  greetingFor()
    -> hour as Integer
    <- rtn as String: String()        // unset until a branch sets it
    if hour < NOON
      rtn := "Good morning"
    else if hour < EVENING
      rtn := "Good afternoon"
    rtn :=? "Hello, World"            // the evening falls through unset: only then is the default used
  greetingFor(9)    // "Good morning"   - :=? skipped
  greetingFor(14)   // "Good afternoon" - :=? skipped
  greetingFor(21)   // "Hello, World"   - :=? assigned

After the if / else if, rtn MIGHT be set: two paths set it and the fall-through does not. That is what :=? is for - it decides at run time, so the default never overwrites a greeting a branch chose.

A VALUE THAT IS ALREADY SET IS KEPT

  loadStoredTitle()
    <- rtn as String: "Untitled"
  title <- loadStoredTitle()          // already set: "Untitled"
  title :=? "Default Title"           // SKIPPED, because title is set
  stdout.println(title)               // still "Untitled"

WHEN THE COMPILER ALREADY KNOWS THE ANSWER

:=? is for a value that MAY be unset. Where the compiler can see the outcome it says so:
- straight after 'greeting as String: String()' the target is always unset, so ':=?' always applies - a plain assignment written as a conditional one (E08097). Write greeting := "Hello, World", or declare it with the value: greeting as String: "Hello, World".
- on a target it can see is set (title <- "Untitled", from a literal), it can never apply (E08095).
A variable declared with no value at all ('greeting as String?') is the exception: filling that empty slot is what :=? is for, so neither error applies.

WHY NOT IF + ASSIGNMENT?

The long-form equivalent works but is more verbose:

  if not rtn?
    rtn := "Hello, World"

The :=? operator collapses this into one line. It is also IMPOSSIBLE to forget the negation - there is only one direction the operator can go.

NOT TO BE CONFUSED WITH OTHER OPERATORS

- := is plain assignment (always assigns, overwrites)
- :=? is assign-if-unset (assigns only if unset)
- <- is declaration + first assignment (creates a new variable)
- <? is coalescing minimum (RETURNS the smaller value, completely different family)

See Q1264 for Integer assign-if-unset. See Q1265 for Date. See Q1266 for custom record. See Q1226 for the contrast between :=? and <?.

Example

#!ek9
defines module qa.operators.guardedassignstring

  defines constant

    NOON <- 12
    EVENING <- 18

  defines function

    //Two paths set the greeting and the evening falls through without one: only at run time is it known
    //whether rtn is set, so ':=?' really decides - the default is used for the evening alone.
    greetingFor()
      -> hour as Integer
      <- rtn as String: String()
      if hour < NOON
        rtn := "Good morning"
      else if hour < EVENING
        rtn := "Good afternoon"
      rtn :=? "Hello, World"

    //Sourced rather than a literal, so 'title' is genuinely already-set at runtime without
    //the compiler being able to prove it and reject the ':=?' below as dead (E08095).
    loadStoredTitle()
      <- rtn as String: "Untitled"

  defines program

    GuardedAssignStringDemo()
      stdout <- Stdout()

      stdout.println(`morning :=? : ${greetingFor(9)}`)
      stdout.println(`afternoon :=? : ${greetingFor(14)}`)
      stdout.println(`evening :=? : ${greetingFor(21)}`)

      //Already set when it arrives: ':=?' keeps it
      title <- loadStoredTitle()
      title :=? "Default Title"
      stdout.println(`pre-set :=? : ${title}`)

Common mistakes

E08097 — String() makes an unset String, so the ':=?' after it always applies - a plain ':=' written as a conditional one. Declare the variable with the value, or keep ':=?' for a value that MAY be unset (from a call, or after a branch that may not set it). See ek9 -h E08097 for details.

Incorrect:

      title <- String()

Correct:

      title <- loadStoredTitle()

E08095 — :=? only assigns when the target is unset. A title declared from a literal is always set, so the compiler rejects the assign-if-unset as one that can never apply (E08095) - and from String() it would always apply (E08097). Take it from where it may or may not be set. See ek9 -h E08095 for details.

Incorrect:

      title <- "Untitled"

Correct:

      title <- loadStoredTitle()
Other ways to ask this
  • How do I assign a default to a String only when it is unset?
  • Show me the EK9 way to provide a fallback value for an unset String.
  • What is the operator that assigns only when the target is unset?
  • Give me a worked example of conditional default assignment on a String.

Coming from another language?

Java: if (variable == null) variable = default. Kotlin: variable = variable ?: default. Python: variable = variable or default (works but conflates falsy with None). Rust: variable = variable.unwrap_or(default). JavaScript: variable = variable ?? default (nullish coalescing assignment). EK9: variable :=? default — direct, single operator, only assigns when target is unset.

Keywords: :=?, String, assign-if-unset, conditional assignment, default value, fallback, unset