TODO.swift

I saw this Swift trick a few years ago on Twitter. I can’t recall who it was, sorry. It sounded way too clever at first, but after a while I tried and decided I love it. Now it’s one of those things I add to every project.

Sometimes you just want to get on with declaring your functions without worrying about the actual implementations. A small enough function with good enough name and types is pretty much done. Filling out the body is just something that will distract you and make you lose the big picture by bogging you down in the details.

The problem with incomplete functions is that they make the type checker unhappy. Have a return type but no return statement? That’s a compilation error. And the more interesting your types, the harder it is to make a temporary hack to placate the compiler.

Swift offers an escape hatch: the Never type. Call a function that returns Never and you don’t need to worry about writing a return. So this will compile:

func foo(zap: Bar) -> Fnord {
    fatalError("Unimplemented")
}

The code will compile, it will just crash at run time. Exactly what we wanted. But we can do better.

You can write your own function that returns Never as long as you ensure it never returns. Like, for example, by calling fatalError as we did in the previous example:

func TODO() -> Never { fatalError("Unimplemented") }

func foo(zap: Bar) -> Fnord {
    TODO()
}

That’s an clear improvement: less need for repeating yourself, and easier to find in the code. The next small step will be to ditch the parentheses by switching to a computed property:

var TODO: Never { fatalError("Unimplemented") }

func foo(zap: Bar) -> Fnord {
    TODO
}

Not a huge improvement, but better, anyway.

There’s one more thing we can do: we can make it harder to forget these in the code. TODO doesn’t cause errors, but we can make it cause a warning by abusing Swift’s deprecation annotation. And because you care for the quality of your code base you fix all warnings and so can’t help but notice the new ones when you compile.

@available(*, deprecated, message: "Unimplemented")
var TODO: Never { fatalError("Unimplemented") }

func foo(zap: Bar) -> Fnord {
    TODO
}

Now the TODO line inside Foo will cause a warning:

'TODO' is deprecated: Not implemented

You’ll get a nice warning reminding you to implement the function before merging your code in, but you can do it when you’re ready.

© Juri Pakaste 2023