Swift Digest
SE-0477 | Swift Evolution

Default Value in String Interpolations

Proposal
SE-0477
Authors
Nate Cook
Review Manager
Xiaodi Wu
Status
Implemented (Swift 6.2)

01 何が問題だったのか

文字列補間(string interpolation)は、文字列リテラルの中に値を埋め込むための簡潔で強力な手段です。しかし埋め込みたい値がオプショナルの場合は話が一気に厄介になり、見苦しいコードを書くか、デバッグ用の出力をそのまま見せるかのいずれかになりがちです。

たとえばオプショナルな文字列をそのまま補間すると、警告と二つの修正候補が表示されます。

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?
// note: use 'String(describing:)' to silence this warning
// note: provide a default value to avoid this warning

一つ目の String(describing:) は警告は消えますが、値が nil のときに出力に nil という文字列がそのまま現れてしまい、ユーザー向けの表示には不向きです。二つ目の nil 合体演算子(??)はデフォルト値を自由に指定できるので、文字列の場合はこれで十分です。

let name: String? = nil
print("Hello, \(name ?? "new friend")!")

問題は、?? がオプショナルと同じ型の値しか受け取れないことです。オプショナルな Int に対して「値がないときの表示文字列」を与えるといった用途では素直に書けず、次のような回り道を強いられていました。

let age: Int? = nil
// Optional.map を使う
print("Your age: \(age.map { "\($0)" } ?? "missing")")
// 三項演算子で書く
print("Your age: \(age != nil ? "\(age!)" : "missing")")
// if-let で場合分けする
if let age {
    print("Your age: \(age)")
} else {
    print("Your age: missing")
}

いずれも、「値がなければ代わりにこの文字列を出したい」という意図に対しては不釣り合いに大げさで、文字列補間の手軽さが損なわれてしまいます。

02 どのように解決されるのか

標準ライブラリに、オプショナル値に対してデフォルトの文字列を指定できる文字列補間のオーバーロードを追加します。値の型に関係なく、nil のときに表示したい文字列をそのまま書けるようになります。

let age: Int? = nil
print("Your age: \(age, default: "missing")")
// Your age: missing

値があれば通常どおり補間され、nil のときだけ default: に渡した文字列が使われます。

let age: Int? = 30
print("Your age: \(age, default: "missing")")
// Your age: 30

ラベル名の default: は、同じ目的を持つ Dictionary のサブスクリプト(dict[key, default: ...])に揃えられています。

実装イメージ

このオーバーロードは、DefaultStringInterpolation に追加される appendInterpolation として次のように実装されます。デフォルト値は @autoclosure で受け取られるため、値が存在するときには評価されません。

extension DefaultStringInterpolation {
    mutating func appendInterpolation<T>(
        _ value: T?,
        default: @autoclosure () -> String
    ) {
        if let value {
            self.appendInterpolation(value)
        } else {
            self.appendInterpolation(`default`())
        }
    }
}

導入にあたって

この API は Swift 6.2 の標準ライブラリに追加され、back deploy 対応されているため、古い OS でも利用できます。プロジェクトやライブラリが同名の補間オーバーロードを独自に持っていた場合は、そちらが優先されます。

将来への見通し

仕様書では、文字列補間で String(describing:) ではなく String(reflecting:) を使いたい場面や、そのバリアントにもデフォルト値を添えたい場面が Future Directions として挙げられています。今回の提案のスコープ外ですが、同じ方向の拡張として追加のオーバーロードが検討される可能性があります。また、DefaultStringInterpolation 以外の StringInterpolationProtocol 実装(ログや属性付き文字列など)へ同様のパターンを広げるかどうかは、それぞれの型の事情に応じて個別に判断されます。