Swift Optional Chaining in Arguments

In Swift, optional chaining can only be used for methods, properties, subscripts, etc. of variables. While this is a great feature, it is lacking when it comes to functional paradigms such as those used in many C APIs (or even some pure Swift code). Here is a code sample demonstrating what I mean (I recommend pasting it into a playground and turning on "Render Documentation").

Does anybody want to make this into a proposal?

/*:
Optional Chaining: Arguments
--------
Here we have a value, which may or may not be nil:
*/
var str: String? = "Mystery Value"
//: We can easily call methods of this potentially nil object using optional chaining:
str?.append(Character("✨"))
//: But what about when we want to pass it to a function?
func giveMeAString(_ string: String) {
    print("You gave me a string! Here it is: \(string)")
}
// error: value of optional type 'String?' not unwrapped; did you mean to use '!' or '?'?
// giveMeAString(str)
//: We have to use this horrible construct for every potentially nil argument:
if let notNil = str {
    giveMeAString(notNil)
}
//: The only good way around this is making those functions methods:
extension String {
    func giveMeAStringMethod() {
        print("You gave me a string in a method! Here it is: \(self)")
    }
}
str?.giveMeAStringMethod()
//: But even if we made this function a method of `String`, we would have
//: the same problem with any of its arguments.
extension String {
    func giveMeAnIntToo(_ int: Int) {
        print("Here's a string: '\(self)' and an int: \(int)")
    }
}
let int: Int? = 3

// error: value of optional type 'Int?' not unwrapped; did you mean to use '!' or '?'?
// str?.giveMeAnIntToo(int)

// Workaround:
if let notNil = int {
    str?.giveMeAnIntToo(notNil)
}

//: And for many APIs, this becomes a huge problem.
/*:
We should be able to use optional chaining for arguments.
--------
It could be as simple as `giveMeAString(str?)`, and `str?.giveMeAnIntToo(notNil?)`.
It would alleviate many of these problems, and make the usage of many APIs cleaner.
*/
//: - - -
//: Example:
let i: Int? = -7
// error: cannot invoke 'abs' with an argument list of type '(Int?)'
// print(abs(i) ?? "i is nil")
// Workaround:
if let i = i {
    print(abs(i))
} else {
    print("i is nil")
}
//: Solution: `print(abs(i?) ?? "i is nil")`
//: - - -
//: Example:
var numbers = [1, 2, 3]
let next: Int? = 4
// error: value of optional type 'Int?' not unwrapped; did you mean to use '!' or '?'?
// numbers.append(next)
// Workaround:
if let next = next {
    numbers.append(next)
}
//: Solution: `numbers.append(next?)`
//: - - -
//: Example:
func average(_ array: [Int]) -> Double {
    return Double(array.reduce(0, +))/Double(array.count)
}

func randomNumbers() -> [Int]? {
    if (arc4random_uniform(2) == 0) {
        return nil
    }

    var result: [Int] = []
    for _ in 0..<5 {
        result.append(Int(arc4random_uniform(100)))
    }
    return result
}

// error: value of optional type '[Int]?' not unwrapped; did you mean to use '!' or '?'?
// print(average(randomNumbers()) ?? "got no random numbers")
// Workaround:
if let numbers = randomNumbers() {
    print(average(numbers))
} else {
    print("got no random numbers")
}
//: Solution: `print(average(randomNumbers()?) ?? "got no random numbers")`
//: - - -
//: Example:
import Foundation
import DiskArbitration
// So many errors:
// String(cString: DADiskGetBSDName(DADiskCreateFromVolumePath(nil,
//                                                            DASessionCreate(nil),
//                                                            URL(fileURLWithPath: "/") as CFURL)))
// Workaround:
if let session = DASessionCreate(nil) {
    if let disk = DADiskCreateFromVolumePath(nil, session, URL(fileURLWithPath: "/") as CFURL) {
        if let cName = DADiskGetBSDName(disk) {
            print(String(cString: cName))
        }
    }
}
//: Solution: `print(String(cString: DADiskGetBSDName(DADiskCreateFromVolumePath(nil,
//:                                                                              DASessionCreate(nil)?,
//:                                                                              URL(fileURLWithPath: "/") as CFURL)?)?))`

To start, if you want to propose ideas for the future of Swift the best place to do that is the [swift-evolution][refSU] mailing list.

In terms of how you can improve your code today, my recommendation is that you try to avoid generating optionals in the first place. I tend to reserve optionals for the periphery of my code (when I’m taking input from untrusted sources) and thus my core code rarely needs to deal with optionals unless there an actual part of the data model, and then the right behaviour tends to just fall out.

Beyond that, there are a bunch of existing techniques for managing optionals that you can employ:

  • Nil-coalescing operator (

    ??
    )
  • Empty collections rather than optional collections

  • Optional.flatMap(_:)
  • Multi-item condition lists

I’m not going to go through your full list of examples because they don’t give a good feel for how things would hold together in practice. If you find yourself wrangling a lot of optionals then the above-mentioned techniques do tend to be a little clumsy. My response is to think about ways to eliminate (or isolate) the optionals altogether.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
Swift Optional Chaining in Arguments
 
 
Q