Force optionals in multi-unwrapped 'guard let' or 'if let'
Mixing optional and non-optional functions in a guard is impossible and requires breaking up the lines. Here's a neat solution on how to circumvent this.
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(),
let numberThree = array[2],
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(),
let elements = Optional.Some(aString.characters.split(\"/\")),
let last = elements.last,
let count = Optional.Some(last.characters.count),
count == 5 else { fatalError(\"Wrong Path\") }
print(\"We have \(count) items in \(last)\")
Or you start refactoring this into seperate closures or functions, but that's an awful lot of work for just one guard statement