Why is 'name <- String()' then 'name :=? "Steve"' reported as an error (E08097)?

← Data Flow Safety · Ref: Q2254

'String()' makes a String that is present but UNSET, so a ':=?' straight after it always assigns - the 'only if unset' part can never decide anything. It is a plain assignment written as a conditional one, and a reader looks for the case where it does not assign. The compiler is sure, so it reports E08097 at the ':=?':

  name <- String()
  name :=? "Steve"     //E08097: 'name' is always unset here
  Stdout().println(name)

Say what the code does: declare the variable with the value ('name <- "Steve"'), or assign it with ':='. Keep ':=?' for a value that MAY already be set - one that came from a function, a parameter or a lookup.

A FALLBACK CHAIN

The usual case is the head of a chain: 'setting <- String()' then 'setting :=? fromEnvironment()' then 'setting :=? fromFile()'. Starting from 'String()' makes the first ':=?' always apply, so it is E08097. Start the chain from its first source instead - 'setting <- fromEnvironment()' then 'setting :=? fromFile()' then 'setting :=? "default"' - and every ':=?' after it genuinely decides, as the program below shows.

INSIDE 'if not x?'
There the value is unset by the condition itself, so give it a value with ':='.

NEVER REPORTED

A variable declared with NO value - 'label as String?', a returning variable '<- rtn as String?', or a property declared that way and filled in a constructor - holds no value at all until it is first written, and ':=?' is the form that fills it. In a 'pure' scope ':=' is not allowed (E08100), so ':=?' is never reported there. A guard 'if x :=? f()' also gates its block, so it is never reported either.

ONLY WHEN SURE

EK9 has no warnings, so an error must be certain. A value from a function call may be set, so the compiler stays silent; in a loop the value must be unset on every pass. The mirror is E08095: ':=?' on a target that is always SET can never apply. See Q79 for ':=?', Q1038 for fallback chains and Q2253 for E08096.

Example

#!ek9
defines module qa.dataflowsafety.assignifunsetalwaysapplies

  defines function

    //Nothing in the environment: unset
    fromEnvironment()
      <- rtn as String: String()

    fromFile()
      <- rtn as String: "from file"

    resolveSetting()
      <- rtn as String: String()
      setting <- fromEnvironment()
      setting :=? fromFile()
      setting :=? "default"
      rtn := setting

    retriesOrDefault()
      -> configured as Integer
      <- retries as Integer: configured
      if not retries?
        retries := 3

  defines program

    AssignIfUnsetAlwaysAppliesDemo()
      stdout <- Stdout()

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

      //Declared with no value: ':=?' is the form that fills it
      label as String?
      label :=? "Untitled"
      stdout.println(`label: ${label}`)

      stdout.println(`retries: ${retriesOrDefault(Integer())}`)
      stdout.println(`kept: ${retriesOrDefault(5)}`)

      //Known to be unset: declare it with the value instead
      nickname <- "Steve"
      stdout.println(`nickname: ${nickname}`)

Common mistakes

E08097 — String() makes an unset String, so the first ':=?' of the chain always applies. Start the chain from its first source. See ek9 -h E08097 for details.

Incorrect:

      setting <- String()
      setting :=? fromEnvironment()
      setting :=? fromFile()

Correct:

      setting <- fromEnvironment()
      setting :=? fromFile()

E08097 — Inside 'if not retries?' the value is unset, so ':=?' always applies. Give it the value with ':='. See ek9 -h E08097 for details.

Incorrect:

        retries :=? 3

Correct:

        retries := 3
Other ways to ask this
  • What does E08097 ':=?' always applies mean?
  • Why does the compiler ask for ':=' instead of ':=?' here?
  • Why is the first ':=?' of my fallback chain an error?
  • When does EK9 report an assign-if-unset that always assigns?

Coming from another language?

Kotlin: x = x ?: default and C#/JavaScript: x ??= default assign only when x is null - and on a value just set to null they always assign, which no compiler reports. EK9 has no null; 'String()' is a present but UNSET String, and a ':=?' the compiler can see always applies is reported as E08097. Use ':=' or declare with the value.

Keywords: :=?, E08097, Integer(), String(), always applies, always unset, assign if unset, data flow, fallback chain, unset