How do I migrate Swift optional binding chains to EK9?

← Safe Value Access · Ref: Q747

Swift has five optional unwrapping patterns. EK9 replaces all of them with guard expressions and coalescing operators, with zero runtime crash risk.

SWIFT PATTERN 1: IF LET (SINGLE BINDING)

  Swift:  if let name = getName() { use(name) }
  EK9:    if name <- getName()
            use(name)

EK9's guard expression is the direct equivalent. The variable only exists inside the if block. Both skip the block when the value is absent or unset.

SWIFT PATTERN 2: IF LET CHAIN (MULTIPLE BINDINGS)

  Swift:  if let a = getA(), let b = getB() { use(a, b) }
  EK9:    if first <- getA()
            if second <- getB()
              use(first, second)

Swift chains multiple bindings in one if. EK9 uses nested guards. Each level adds a guard check.

SWIFT PATTERN 3: GUARD LET (EARLY EXIT)

  Swift:  guard let name = getName() else { return nil }
  EK9:    if name <- getName()
            processName(name)

EK9 has no return statement. Instead of guard-else-return, put the success path inside the if block. The else block (or simply falling through) handles the failure case.

SWIFT PATTERN 4: NIL COALESCING

  Swift:  let name = getName() ?? "default"
  EK9:    name <- getName() ?: "default"

Swift's ?? is a memory-level nil check. EK9's ?: checks isSet (more thorough). EK9 also has ?? for memory-level checks, plus <? and >? for coalescing comparisons.

SWIFT PATTERN 5: FORCE UNWRAP

  Swift:  let name = getName()!
  EK9:    (no equivalent: force unwrap does not exist)

EK9 has no force unwrap. There is no escape hatch. You must use a guard or coalescing operator. This eliminates the entire category of force-unwrap crashes.

SWIFT PATTERN 6: OPTIONAL CHAINING

  Swift:  let len = person?.address?.street?.count
  EK9:    if person <- getPerson()
            if addr <- person.address()
              streetLen <- length addr.street()

EK9 uses nested guards instead of ?. chaining. Each guard is explicit and checked by the compiler.

See Q47 for Optional basics. See Q74 for if guard patterns. See Q75 for switch guards. See Q79 for guarded assignment. See Q168 for fallback values. See Q243 for coalescing operators.

Example

defines module qa.safeaccess.swiftoptional

  defines function

    <?-
      Simulates a lookup that might not find a value.
    -?>
    findUser() as pure
      -> userId as Integer
      <- rtn <- Optional() of String

      aliceId <- 1
      bobId <- 2

      if userId == aliceId
        rtn: Optional("Alice")
      else if userId == bobId
        rtn: Optional("Bob")

    <?-
      Simulates an address lookup that might not find a value.
    -?>
    findAddress() as pure
      -> name as String
      <- rtn <- Optional() of String

      if name == "Alice"
        rtn: Optional("123 Main Street")

  defines program

    SwiftOptionalMigrationDemo()
      stdout <- Stdout()

      // === PATTERN 1: IF LET (SINGLE BINDING) ===
      // Swift: if let user = findUser(1) { print(user) }

      if user <- findUser(1)
        stdout.println(`Found user: ${user.get()}`)

      // === PATTERN 2: IF LET CHAIN (NESTED GUARDS) ===
      // Swift: if let user = findUser(1), let addr = findAddress(user) { ... }

      if user <- findUser(1)
        if addr <- findAddress(user.get())
          stdout.println(`${user.get()} lives at ${addr.get()}`)

      // === PATTERN 3: GUARD LET BECOMES IF GUARD ===
      // Swift: guard let user = findUser(3) else { return }
      // EK9: success path inside the guard block

      if user <- findUser(3)
        stdout.println(`User 3: ${user.get()}`)
      else
        stdout.println("User 3 not found")

      // === PATTERN 4: NIL COALESCING ===
      // Swift: let name = findUser(99) ?? "Anonymous"

      lookup <- findUser(99)
      userName <- lookup.getOrDefault("Anonymous")
      stdout.println(`User 99: ${userName}`)

      // === NO FORCE UNWRAP ===
      // Swift: let name = findUser(1)!  (crashes if nil)
      // EK9: no equivalent. Must use guard or coalescing.

Common mistakes

E50060 — Optional has no unwrap() method. Use getOrDefault() for safe access with a fallback. EK9 has no force unwrap. See ek9 -h E50060 for details.

Incorrect:

userName <- lookup.unwrap()

Correct:

userName <- lookup.getOrDefault("Anonymous")

E50060 — Optional has no getValue() method. Use get() inside a guard block. See ek9 -h E50060 for details.

Incorrect:

if user <- findUser(1)
        stdout.println(`Found user: ${user.getValue()}`)

Correct:

if user <- findUser(1)
        stdout.println(`Found user: ${user.get()}`)
Other ways to ask this
  • What is the EK9 equivalent of Swift if let?
  • How do I translate Swift guard let to EK9?
  • How do I replace Swift optional chaining in EK9?
  • What replaces Swift's force unwrap in EK9?

Coming from another language?

Swift: if let for binding, guard let for early exit, ?? for nil coalescing, ! for force unwrap (crash risk), ?. for optional chaining. Kotlin: ?.let{} for binding, ?: for elvis, !! for force unwrap (NPE risk). Java: Optional.map().orElse() chains, .get() throws NoSuchElementException. Rust: if let Some(v) for binding, unwrap() for panic, ? operator for propagation. EK9: guard expressions (if x <- expr()) for binding, ?: for isSet coalescing, ?? for memory coalescing, no force unwrap, nested guards for chaining.

Keywords: null-safe, force, nil, migrate, optional, let, binding, swift, safe, unwrap, guard, coalescing, chain