Lightning Read #2: Optional String Pitfalls
By: Göksel Köksal
Tags: Swift, Programming, iOS, String, Tips

Photo by Max Bender on Unsplash

I was trying to build a URL the other day. Check the following code block:

init?(userId: String?) {

guard userId != nil else { return nil }



self.path = "/user/\(userId)"

self.url.appendPathComponent(self.path)

}

Seems legit, right?

Assume that we’re passing 23940 as userId to this initializer. Depending on the Swift version, path value would be:

Swift 2.x | path = "/user/23940"

Swift 3.x | path = "/user/Optional("23940")"

Swift 4.x | path = "/user/Optional("23940")"

Subtle, but heart-breaking.

Firstly, beware of this issue if you’re working on a super-old project which still uses Swift 2.x for some reason, and planning to migrate.

Secondly, we could use a guard-let instead of a guard-check statement here to fix this issue. However, it’s not convenient to have guard statements everywhere, especially when you simply want to print stuff.

I created the following struct to overcome this problem:

public struct Printable<T>: CustomStringConvertible {



public let value: T?



public init(_ value: T?) {

self.value = value

}



public var description: String {

if let value = value {

return String(describing: value)

} else {

return "(null)"

}

}

}

Note: Yes, we could also print “nil” instead of “(null)” but I think the latter is more expressive. You can always choose this one or the other.

Whenever you have optionals, you simply wrap them in a Printable struct as below.

self.path = "/user/\(Printable(userId))" // "/user/23940"

print(Printable(userId)) // "23940"

Or, in a more complex example:

struct User: CustomStringConvertible { let id: String

let firstName: String?

let lastName: String?



var description: String {

var string = id

string += ", \(Printable(firstName))"

string += ", \(Printable(lastName))"

return string

}

} let user1 = User(

id: "23940",

firstName: "Göksel",

lastName: "Köksal"

) let user2 = User(

id: "23941",

firstName: "Sıla",

lastName: nil

) print(user1)

print(user2)

Without Printable struct, these objects would print below;

"23940, Optional("Göksel"), Optional("Köksal")"

"23941, Optional("Sıla"), nil"

With Printable , it prints;

"23940, Göksel, Köksal"

"23941, Sıla, (null)"

The latter seems much cleaner.

Alternatives

Global “describe” Function

func describe<T>(_ value: Optional<T>) -> String {

switch value {

case .some(let wrapped):

return String(describing: wrapped)

case .none:

return "(null)"

}

}

Optional Extension

extension Optional {



var stringValue: String {

switch self {

case .some(let wrapped):

return String(describing: wrapped)

case .none:

return "(null)"

}

}

}

Credit: Alp Avanoğlu

Comparison

var url1: URL? = URL(string: "www.google.com")

var url2: URL? = nil // `Printable` struct: print(Printable(url1)) // Prints "www.google.com"

print(Printable(url2)) // Prints "(null)" // `describe` global function: print(describe(url1)) // Prints "www.google.com"

print(describe(url2)) // Prints "(null)" // `stringValue` extension on `Optional`: print(url1.stringValue) // Prints "www.google.com"

print(url2.stringValue) // Prints "(null)"

Moral of the Story