Do you know this problem? You want to display an optional value in the UI or log it to the console for debugging, but you don’t like the default string conversion for optionals, which results in either "Optional(…)"
or "nil"
. For example:
var someValue: Int? = 5
print("The value is \(someValue)")
// → "The value is Optional(5)"
someValue = nil
print("The value is \(someValue)")
// → "The value is nil"
Using optionals in string interpolation can yield unexpected results
Swift 3.1 will actually actually raise a warning when you use an optional in string interpolation like this because this behavior may be surprising. Here’s Julio Carrettoni, Harlan Haskins, and Robert Widmann’s argument on swift-evolution:
Given that the
Optional
type is never fit for display to the end user, and can often be a surprising find in the console, we propose that requesting anOptional
’s debug description be an explicit act. This proposal now requires a warning when using an expression ofOptional
type within a string interpolation segment.
The warning is already implemented in the latest Swift development snapshot (2016-12-01):
You have several options to silence the warning:
- Add an explicit cast, as in
someValue as Int?
. - Use
String(describing: someValue)
. - Provide a default value to make the expression non-optional, as in
someValue ?? defaultValue
.
I don’t particularly like any of these in most cases, but it’s the best the compiler can offer. The problem with the third option is that the nil-coalescing operator ??
requires matching types — if the left operand is a T?
, the right operand must be a T
. Applied to the example above, this means I can provide another Int
as a default value, but not a string — which is what I’d like to do in this situation.
A custom optional-string-coalescing operator
I solved this by defining my own custom optional-string-coalescing operator. I decided to name it ???
because of its obvious connection to the nil-coalescing operator.1 The ???
operator takes any Optional
on its left side and a default string value on the right, returning a string. If the optional value is non-nil
, it unwraps it and returns its string description, otherwise it returns the default value. Here’s the implementation:
infix operator ???: NilCoalescingPrecedence
public func ???<T>(optional: T?, defaultValue: @autoclosure () -> String) -> String {
switch optional {
case let value?: return String(describing: value)
case nil: return defaultValue()
}
}
The @autoclosure
construct ensures that the right operand is only evaluated when needed, i.e. when the optional is nil
. This allows you to pass an expression that is expensive or has side effects and only incur the performance cost in the rare case. I don’t think it’s too important to support this for this use case, but it mirrors the definition of the ??
operator in the standard library. (I decided to leave out the throws
/rethrows
dance the standard library version does, though.)
Alternatively, you could implement the operator with a one-liner using Optional.map
, like so:
public func ???<T>(optional: T?, defaultValue: @autoclosure () -> String) -> String {
return optional.map { String(describing: $0) } ?? defaultValue()
}
This is completely equivalent to the first implementation. Which version you prefer is a matter of taste and what coding style you’re used to. I don’t think either is significantly clearer than the other.
One last thing I’d like to note is that you have to make a choice whether you want to use String(describing:)
(i.e. preferring the value’s description
or String(reflecting:)
(preferring debugDescription
) to convert the value. The former is better suited for formatting a value for UI display, whereas the latter may be the better choice for logging. Alternatively, you could define another operator (say, ????
) for the usually longer debug description output.
In action
Let’s rewrite the example from the beginning of this article with the ???
operator:
var someValue: Int? = 5
print("The value is \(someValue ??? "unknown")")
// → "The value is 5"
someValue = nil
print("The value is \(someValue ??? "unknown")")
// → "The value is unknown"
It’s a small thing, but I really like this.
-
I initially thought it a good idea to overload the nil-coalescing operator, i.e. define a version of
??
that has the type(T?, String) -> String
. I liked this because I think my implementation captures the meaning of the nil-coalescing operator very well, but it would have meant giving up some type safety in other contexts, since an expression likesomeOptional ?? "someString"
would now always compile. ↩︎