The Beauty of Swift 5 String Interpolation

Thanks to SE-0228, you can control exactly how you want string interpolations to print. Thank you Brent for giving this to us. It’s a doozy! Let me share some examples…

The Original

Consider printing optionals. You use:

1
"There's \(value1) and \(value2)"

And get an instant warning:

1

You can click the fixes to suppress the warning but you’ll still get an output that looks like this: “There’s Optional(23) and nil”.

1
"There's \(String(describing: value1)) and \(String(describing: value2))"

Now you can strip the “Optional” and produce “There’s 23 and nil”, allowing you to print values directly:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
extension String.StringInterpolation {
/// Provides `Optional` string interpolation without forcing the
/// use of `String(describing:)`.
public mutating func appendInterpolation(_ value: T?, default defaultValue: String) {
if let value = value {
appendInterpolation(value)
} else {
appendLiteral(defaultValue)
}
}
}
// There's 23 and nil
"There's \(value1, default: "nil") and \(value2, default: "nil")"

You might create a set of styles and custom interpolation to support consistent presentation for optional output:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
extension String.StringInterpolation {
/// Optional Interpolation Styles
public enum OptionalStyle {
/// Includes the word `Optional` for both `some` and `none` cases
case descriptive
/// Strips the word `Optional` for both `some` and `none` cases
case stripped
/// Uses system interpolation, which includes the word `Optional` for
/// `some` cases but not `none`.
case `default`
}
/// Interpolates optional values using a supplied `optStyle` style
public mutating func appendInterpolation(_ value: T?, optStyle style: String.StringInterpolation.OptionalStyle) {
switch style {
// Includes the word `Optional` for both `some` and `none` cases
case .descriptive:
if value == nil {
appendLiteral("Optional(nil)")
} else {
appendLiteral(String(describing: value))
}
// Strips the word `Optional` for both `some` and `none` cases
case .stripped:
if let value = value {
appendInterpolation(value)
} else {
appendLiteral("nil")
}
// Uses system interpolation, which includes the word `Optional` for
// `some` cases but not `none`.
default:
appendLiteral(String(describing: value))
}
}
/// Interpolates an optional using "stripped" interpolation, omitting
/// the word "Optional" from both `.some` and `.none` cases
public mutating func appendInterpolation(describing value: T?) {
appendInterpolation(value, optStyle: .stripped)
}
}
// "There's Optional(23) and nil"
"There's \(value1, optStyle: .default) and \(value2, optStyle: .default)"
// "There's Optional(23) and Optional(nil)"
"There's \(value1, optStyle: .descriptive) and \(value2, optStyle: .descriptive)"
// "There's 23 and nil"
"There's \(describing: value1) and \(describing: value2)"

Interpolation isn’t limited to tweaking optionals. Say you want to control whether a string is added without having to use a ternary expression with an empty string:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Include on success (Thanks, Nate Cook)
extension String.StringInterpolation {
/// Provides Boolean-guided interpolation that succeeds only when the condition
/// evaluates to true.
mutating func appendInterpolation(if condition: @autoclosure () -> Bool, _ literal: StringLiteralType) {
guard condition() else { return }
appendLiteral(literal)
}
}
// Old
"Cheese Sandwich \(isStarred ? "(*)" : "")"
// New
"Cheese Sandwich \(if: isStarred, "(*)")"

There’s lots more you can do and it’s all exciting and fun.

Thx F Sup