How to set a default value in Swift string interpolation
Table of Contents
Interpolating an optional gets us a warning we have all seen.
let name: String? = nil
print("Hello, \(name)!")
// warning: string interpolation produces a debug description for an optional value;
// did you mean to make this explicit?
Swift offers two fixes, and neither is great. SE-0477, in Swift 6.2, adds a third.
let name: String? = nil
print("Hello, \(name, default: "new friend")!")
// Hello, new friend!
What was wrong with the old fixes
The compiler suggests two things.
The first is String(describing:).
print("Hello, \(String(describing: name))!")
// Hello, nil!
This silences the warning by printing nil in our user-facing string. Fine in a throwaway script, not fine in an app.
The second suggestion is the nil-coalescing operator, and for strings it works well.
print("Hello, \(name ?? "new friend")!")
The catch is that ?? requires the default to have the same type as the optional. That's no problem for a String?, but it falls apart the moment the value isn't a string.
let age: Int? = nil
print("Your age: \(age)")
We want to print missing when there's no age. There is no Int we can put on the right of ?? that means that, so we end up with one of these.
// Optional.map
print("Your age: \(age.map { "\($0)" } ?? "missing")")
// Ternary, with a force unwrap
print("Your age: \(age != nil ? "\(age!)" : "missing")")
// Or give up on the single literal entirely
if let age {
print("Your age: \(age)")
} else {
print("Your age: missing")
}
All three are more ceremony than the idea deserves. The map version has a nested interpolation inside an interpolation, and the ternary reaches for ! in code whose whole purpose is to avoid crashing on nil.
You can easily support sarunw.com by checking out this sponsor.
Localization Buddy: Easiest way to localize and update App Store metadata.
The new syntax
SE-0477 lets us write the fallback as a string regardless of what type the value is.
let age: Int? = nil
print("Your age: \(age, default: "missing")")
// Your age: missing
The default is always a String, so the type of the optional never constrains what we can write.
How it works
The whole feature is one overload on DefaultStringInterpolation.
extension DefaultStringInterpolation {
mutating func appendInterpolation<T>(
_ value: T?,
default: @autoclosure () -> String
) {
if let value {
self.appendInterpolation(value)
} else {
self.appendInterpolation(`default`())
}
}
}
Two details worth noticing.
It is generic over T, so it accepts any optional, not just String?.
And the default is an @autoclosure, so it's only evaluated when the value is actually nil. If the fallback is expensive to produce, or is a localized lookup, we don't pay for it on the happy path.
You may also recognise the default: label from Dictionary's subscript, which was named that way for the same reason. That's deliberate — the proposal picked the label to match.
It back deploys
This one is nicer than it sounds: the new interpolation is marked as backward deployable.
It ships in a new version of the Swift runtime, but we can use it in code that targets older OS versions. So this isn't a feature to file away until the deployment target moves — it's usable as soon as the compiler is.
One thing to watch
If you or one of your dependencies already declared a similar overload, yours takes precedence over the standard library's.
That's good for source compatibility, since nobody's code breaks. But if you added your own \(value, default:) helper at some point and its behavior differs, you'll keep getting yours, quietly. Worth a search before assuming you're calling the new one.
You can easily support sarunw.com by checking out this sponsor.
Localization Buddy: Easiest way to localize and update App Store metadata.
Summary
\(value, default: "fallback") gives any optional a fallback string in an interpolation.
It solves the case ?? never handled well — optionals that aren't strings — and it does so without nested interpolations, force unwraps, or breaking the literal apart into an if let.
The default is lazily evaluated, works with any optional type, and back deploys, so there's little reason not to reach for it.
Read more article about Swift, Swift Evolution, String, or see all available topic
Enjoy the read?
If you enjoy this article, you can subscribe to the weekly newsletter.
Every Friday, you'll get a quick recap of all articles and tips posted on this site. No strings attached. Unsubscribe anytime.
Feel free to follow me on Twitter and ask your questions related to this post. Thanks for reading and see you next time.
If you enjoy my writing, please check out my Patreon https://www.patreon.com/sarunw and become my supporter. Sharing the article is also greatly appreciated.
Become a patron Buy me a coffee Tweet ShareHow to use raw identifiers in Swift
Swift 6.2 lets us write names containing spaces, digits, and punctuation by wrapping them in backticks. Let's see where that helps.