Optional Optionals

April 02, 2015

So here’s a confusing sentence.

With Swift functions you can have optional parameters, you can also have parameters that are optionals, and you can have optional parameters that are optionals.

A rather confused looking Donald Rumsfeld

Not taking the time to think about the 3 different levels of optionality in function parameters had me scratching my head for a few minutes today, but all the options (sorry) are useful and it’s not at all confusing once you remember them.

My scenario was that I created a function that does some stuff and then executes a closure supplied by the caller. Something like

func doSomeStuff(thenDoThis:()->())

Which a caller would call like

doSomeStuff {
	// and then do this stuff
}

But I want to let the caller decide whether they want to supply the closure or not, so if they like they could just call the function and be done.

doSomeStuff()

So let’s make the closure optional. Easy, as with any type in Swift, we can mark it optional by including a ?

func doSomeStuff(thenDoThis:(()->())?)

So then if we go ahead and call

doSomeStuff() // Error: Missing argument for parameter #1 in call

But it was optional, so why the error? Well it wasn’t optional in the sense that I could leave it out, it’s just that it was an optional type which we are still expected to provide every time. As our type is an optional closure with no parameters and no return, we have to supply a closure with no parameters and no return or nil. So we’d actually have to call

doSomeStuff(nil) // this works fine but isn't what we want

So how do you create an optional parameter, one that a caller can decide to leave out? To do that you provide a default value to be used for that parameter, right in the function declaration.

func doSomeStuff(thenDoThis:()->() = defaultClosure)

This means that if the caller doesn’t supply a value for thenDoThis we’ll use defaultClosure instead (assuming defaultClosure is defined elsewhere as a ()->().) We can now happily call the following if we don’t want to supply a closure.

doSomeStuff() // yay!

The behaviour I was interested in though was that if I didn’t supply a closure, that there would be no closure executed at all, not that some other one I had to define would be called instead. Well, I could just make defaultClosure do nothing, or just have the default value be {} like

func doSomeStuff(thenDoThis:()->() = {})

Which is fine, and maybe even the preferred way, but you can also have an optional optional parameter, and have it’s default value be nil.

func doSomeStuff(thenDoThis:(()->())? = nil)

Now if the caller omits the closure, thenDoThis will be nil, which makes more sense to me in this situation.