AppVenture has been updated

All articles have been rewritten and improved. You will be forwarded to the updated article.

Click here to go there directly.

Thu, 14 Apr 2016 #

Force optionals in multi-unwrapped "guard let" or "if let"

I really love unwrapping optionals in a multi- guard or let statement with additional where clauses added. See my previous post on this here. However, sometimes I run into a situation where I have one function call (or a array subscript) in between my others that does not return an optional:

// Imagine this function does something complicated
func someArray() -> [Int]? {
    return [1, 2, 3, 4, 5, 6]
}

func example() {
    guard let array = someArray(),
	numberThree = array[2]
	where numberThree == 3
	else { return }
    print(numberThree)
}

This doesn't work. The compiler will explain to you that it expects an optional:

"Initializer for conditional binding must have Optional type, not 'Int'"

So, what you oftentimes end up with, instead, is something like this:

func example() {
    guard let array = someArray() else { return }
    let numberThree = array[2]
    guard numberThree == 3 else { return }
    print(numberThree)
}

Not only is this awful to look at, you also have to write the failure block twice. That's ok for a simple example as this one { return }, but when you have to perform a bit more work in there you'll have to repeat code blocks; and that's bad 1.

So what's the solution here? Well, since the guard or let requires an optional, we can just as well create one and unpack it again:

func example() {
    guard let array = someArray(),
	numberThree = Optional.Some(array[2])
	where numberThree == 3
	else { return }
    print(numberThree)
}

As you may remember, Swift's optionals are internally more or less enums with a .Some and a .None case. So what we're doing here is creating a new .Some case only to unwrap it again in the very same line: The array[2] expression will be wrapped with Optional.Some and then unwrapped again into numberThree.

There is a wee bit of overhead here, but on the other hand it does allow us to keep the guard or let unwrappings much cleaner.

This obviously doesn't just work with array subscripts like array[3] but also with any non-optional function, i.e.:

guard let aString = optionalString(),
    elements = Optional.Some(aString.characters.split("/")),
    last = elements.last,
    count = Optional.Some(last.characters.count),
    where count == 5 else { fatalError("Wrong Path") }
print("We have \(count) items in \(last)")

Footnotes:

1
Or you start refactoring this into seperate closures or functions, but that's an awful lot of work for just one guard statement

If you read this far, you should follow me (@terhechte)
on Twitter