Harlan Haskins

Software Engineer | About | Projects | Resume | Contact

goto in Swift

Goto is a statement in the C programming language that, despite its usefulness and flexibility, is somewhat despised among modern-day programmers.

goto is a holdover from Assembly programming, where all control flow is explicitly transferred between memory locations. You label the beginning of a set of statements (usually referred to as a 'Basic Block') and transfer execution using some kind of branch or jump statement.

For example (in MIPS32 assembly)

        syscall                 # assume we've just executed a syscall
        move $t0, $v0           # copy the return value of the syscall
        beq $t0, $zero, is_zero # if $t0 == 0, jump to is_zero, else is is_one
is_not_zero:
        # This will only be executed if the result of the syscall is not zero
        j end
is_zero:
        # This will only be executed if the result of the syscall is zero
        j end
end:
        jr $ra

Whew. That was a mouthful. Essentially, we're checking the result of a syscall and jumping to a different execution location based on it. Roughly, in C, this would translate to

    int syscall_ret = syscall() // assume this works ... 
    if (syscall_ret == 0) goto is_zero;

is_not_zero:
    // This will only be executed if the result of the syscall is not zero
    goto end;

is_zero:
    // This will only be executed if the result of the syscall is zero
    goto end;

end:
    return;

Again, a very roundabout way of writing an if-statement.

There's a very common usage pattern for this in languages like C which have fussy ownership rules. Sometimes it's necessary to run many tasks and perform the same cleanup once that's done, no matter what happens. It's a primitive form of error-handling when nothing else exists.

In fact, in AssertMacros.h, Apple defines a set of macros to facilitate this error handling scheme.

From AssertMacros.h

require(assertion, exceptionLabel)
    In production builds, if the assertion expression evaluates to false, goto exceptionLabel

Using these macros, you can safely clean up after many statements. We can't return when there's an error, because otherwise the file would remain unclosed.

    int retval = 0;
    int err = 0;
    FILE *file = fopen("file.txt", "rw");
    err = readfile(file);
    require(err == 0, fail);
    err = processfile(file);
    require(err == 0, fail);
    err = do_another_thing(file);
    require(err == 0, fail);
    err = do_a_third_thing(file);
    require(err == 0, fail);
    goto cleanup; // skip `fail`.
fail:
    retval = err;
cleanup:
    fclose(file);
    return retval;

In this example, there are many tasks that need to happen to the file, and the function needs to exit early if any of them go wrong. Of course, in Swift, we have a comprehensive and very nice error handling mechanism.

do {
  let file = fopen("file.txt", "rw")
  defer { fclose(file) }

  let contents = try file.read()
  try processFile(file)
  try doAnotherThing(file)
  try doAThirdThing(file)
} catch {
  print(error)
}

The defer statement will ensure the file is closed if an error happens.

Implementing goto in Swift

Swift, by design, abstracts away the notion of instructions or semi-predictable instruction locations, and has never implemented any kind of explicit control flow jumping. This is a fantastic decision because it eliminates classes of bugs in production.

However, Swift does offer a few methods of selective control flow: if statements, switch statements, and function calls. We can replicate the style of the goto from before using a switch statement with string cases:

var x = 4
let label = x == 4 ? "isFour" : "isNotFour"
switch label {
  case "isFour":
    print("It's four!")
  case "isNotFour":
    print("It's not four.")
  default: break // We need this because switches with strings can never be exhaustive.
}

However, this doesn't give us a way to change execution to another label. This really only buys us the ability to jump to an execution location from within an existing location. E.g. there is no reswitch statement.

We can, however, take advantage of three features of Swift: inner functions, recursion, and closures.

Because functions are closures in Swift, inner functions wrap references to variables within the containing scope. We can use this to pretend the inner function is just part of the function body. If we declare it right, we can recurse into the inner function with a different label and effectively alter the execution location.

Imagine this function, which prints all numbers from 1 to 50:

func main() {
  var value = 0
  while value < 50 {
    value += 1
    print(value)
  }
}

We can reimplement it using an inner goto function, like so:

func main() {
  var value = 0
  func goto(label: String) {
    switch label {
      case "cond":
        goto(x < 50 ? "body" : "end")
      case "body":
        value += 1
        print(value)
        goto("cond")
      case "end": break
      default: break
    }
  }
  goto("cond")
}

By recursing into goto, we cause another run of the switch statement with a different label. We can do any kind of control flow this way.

However, this isn't a very extensible solution. There's a lot of boilerplate. We need to declare the goto inner function within any function that wants to use it, and the switch statement. It's kinda hard to visualize the control flow (well, it's always hard with goto) with all the switch noise.

We can define a struct that wraps scoped gotos, that maps strings to closures.

struct Goto {
    typealias Closure = () -> Void
    var closures = [String: Closure]()
    mutating func set(label: String, closure: Closure) {
        closures[label] = closure
    }
    func call(label: String) {
        closures[label]?()
    }
}

And we can define a handy operator to avoid writing goto.call("label")

infix operator  { associativity left precedence 140 }
func (goto: Goto, label: String) {
    goto.call(label)
}

And then we can get started using it! We define multiple blocks within the goto we declare, and call into that goto struct from within those blocks.

var x = 0

var goto = Goto()
goto.set("cond") {
    goto  (x < 50 ? "body" : "end")
}
goto.set("body") {
    x += 1
    print(x)
    goto  "cond"
}
goto.set("end") {}

goto  "cond"

It's incredibly bug-prone, fragile, and easy to mess up.

C'est la goto.

Goto.swift is available in the Swift Package Manager.