When do I use <- and when do I use :=? to set a variable safely?

← Control Flow · Ref: Q1039

They solve different problems:

<- CREATES a new variable. Use it when the variable does not exist yet.

  name <- "Alice"           creates name, assigns "Alice"
  if greeting <- getGreeting()  creates greeting in this scope, only enters if SET

:=? REASSIGNS an existing variable, but only if it is currently UNSET. Use it when the variable already exists and you want to fill in a missing value without overwriting an existing one.

  name <- loadFromConfig()   creates name - unset if config has nothing
  name :=? "default"         assigns only if still unset after config

Create it from the first source rather than unset: 'name <- String()' followed by 'name :=? loadFromConfig()' always applies, so the compiler asks for 'name <- loadFromConfig()' (E08097). To declare a variable with no value at all, write 'name as String?' - filling that empty slot is what :=? is for.

Key distinction:

  <- is about CREATION. The variable must not exist yet.
  :=? is about SAFE UPDATE. The variable must already exist. It protects existing values.

Common mistake: using <- when you mean :=?

  name <- loadFromConfig()      WRONG if name already exists in scope
  name :=? loadFromConfig()     RIGHT — safely fills in if unset

Common mistake: using :=? when you mean <-

  name :=? "Alice"              WRONG if name does not exist yet
  name <- "Alice"               RIGHT — creates the variable

In guards (if, switch, while):

  if name <- getGreeting()      declaration guard — creates name, enters if SET
  if name :=? getGreeting()     assign-if-unset — fills name if unset, enters if SET

Example

#!ek9
defines module qa.controlflow.declare.vs.assignifunset

  defines function

    loadFromConfig()
      <- rtn as String: String()
      //Simulate: config returns unset

    loadFromEnvironment()
      <- rtn as String: "env-value"

  defines program

    DeclareVsGuardedDemo()
      stdout <- Stdout()

      // <- creates a new variable - from the first source (config returns unset, so it starts unset)
      setting <- loadFromConfig()

      // :=? fills in from next source (environment returns "env-value")
      setting :=? loadFromEnvironment()

      // :=? preserves existing value (already set, so "fallback" is ignored)
      setting :=? "fallback"

      stdout.println(`Setting: ${setting}`)

      // Contrast: <- in a guard creates a NEW variable
      if greeting <- loadFromEnvironment()
        stdout.println(`Greeting: ${greeting}`)

Common mistakes

E50001 — Use <- to create a new variable; :=? only reassigns a variable that already exists, so using it first leaves the name unresolved. See ek9 -h E50001 for details.

Incorrect:

      setting :=? loadFromConfig()

Correct:

      setting <- loadFromConfig()
Other ways to ask this
  • What is the difference between <- and :=? in EK9?
  • Should I use declaration or assign-if-unset here?
  • How do I choose between <- for new variables and :=? for safe reassignment?

Coming from another language?

No other language has this distinction. Python's walrus := creates AND assigns. EK9 separates creation (<-) from safe update (:=?).

Keywords: assign, create, declare, difference, guarded, safe, unset