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)?)?))`