Can I give an Optional a new value inside the 'if o?' block that checked it?

← Control Flow · Ref: Q1960

No. Inside the scope that proved an Optional, Result or Iterator safe to unwrap ('if o?', or the body of a guard such as 'while o :=? next()'), EVERY store to it is E08040 - an assignment statement, and a nested construct's pre-flow alike:

  o <- Optional("Steve")
  if o?
    o := Optional("Other")                    // E08040 - the statement re-points o
    if o := Optional("Other") then o?        // E08040 - so does a nested head
      stdout.println(o.get())

The safe scope promises that o.get() works. After the store, o holds the NEW value, which nothing checked: an empty one would throw 'No such element' at the next o.get().

WHAT TO DO INSTEAD

- Guard into a NEW variable: 'if replacement <- other()' - the replacement has its own proof.
- Re-point after the safe scope has ended: 'if name := nextName() then name?' below the 'if name?' block.

A CONSTRUCT'S OWN HEAD IS THE PROOF

'while current :=? nextName() then count < limit' fills current once, before the loop (a pre-flow always runs exactly once), and that guard is what makes current.get() safe in the loop. A while, for, switch or try marks its whole scope safe, head included, so the compiler judges the head against the scope AROUND the construct: it is an error only when an enclosing 'if current?' had already proven current safe.

Only a guard head ('<-', ':=?') proves anything by itself: 'if o := next() then ready' assigns blindly, so o.get() in its body is E08030 (Q1963) - write the check into the condition: 'then o? and ready'.

See Q84 for Optional's operations and Q86 for Result guards.

Example

defines module qa.flow.guard.safescope

  defines function

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

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

    noName()
      <- rtn <- Optional() of String

  defines program

    GuardInSafeScopeDemo()
      stdout <- Stdout()

      name <- firstName()
      stdout.println(`before: name set=${name?}`)

      if name?
        stdout.println("checked: " + name.get())
        if replacement <- nextName()
          stdout.println("replacement: " + replacement.get())
        alias <- noName()
        alias := nextName()
        moved <- alias?
        stdout.println(`moved=${moved} alias set=${alias?}`)
        stdout.println("inside: name=" + name.get())

      if name := nextName() then name?
        stdout.println("re-pointed: " + name.get())

      count <- 0
      limit <- 2
      current <- noName()
      while current :=? nextName() then count < limit
        stdout.println("loop: " + current.get())
        count++

      stdout.println("Done")

Common mistakes

E08040 — Inside 'if name?' the name is proven safe to unwrap, and the inner head's ':=' always stores its value into name, so the code after the inner if would unwrap a value nothing checked (an empty one throws 'No such element'). Guard into a NEW variable instead. See ek9 -h E08040 for details.

Incorrect:

        if name := nextName() then name?
          stdout.println("replacement: " + name.get())

Correct:

        if replacement <- nextName()
          stdout.println("replacement: " + replacement.get())

E08040 — An assignment statement is a store too. Re-pointing name inside the scope that checked it is E08040; assign a different variable. See ek9 -h E08040 for details.

Incorrect:

        name := nextName()

Correct:

        alias := nextName()
Other ways to ask this
  • Why is 'if o := other() then o?' inside 'if o?' error E08040?
  • Why can't I re-point a checked Optional inside its safe block?
  • Is 'o := other()' allowed inside a safe access block?
  • Why does a while loop's own 'o :=? next()' head not count as reassignment?
  • How do I try a replacement Optional inside a block where the first one was checked?

Coming from another language?

Kotlin: a smart cast is silently lost when a var is reassigned; the error appears later, at the unchecked use. Rust: 'if let Some(v) = o' binds a separate value and 'o' cannot be reassigned while borrowed. Swift: 'if let' copies, so the original can change without affecting the binding. EK9 unwraps the original variable directly, so it rejects the store that would break the proof - an assignment statement or a nested construct's head.

Keywords: safe access, optional, while, reassignment, re-point, E08040, pre-flow, if o?, guard