Swift Digest
SE-0128 | Swift Evolution

Change failable UnicodeScalar initializers to failable

Proposal
SE-0128
Authors
Xin Tong
Review Manager
Chris Lattner
Status
Implemented (Swift 3.0)

01 何が問題だったのか

UnicodeScalar は Unicode の1つのコードポイントを表す型です。UInt32 などの整数値から UnicodeScalar を作る初期化子が提供されていますが、Swift 3 より前ではこれらが failable ではない(オプショナルを返さない)初期化子として定義されていました。

public struct UnicodeScalar {
    public init(_ value: UInt32)
    public init(_ value: UInt16)
    public init(_ value: Int)
}

問題は、Unicode として無効なコードポイントを渡したときの挙動です。標準ライブラリは内部で _precondition を呼び出してプログラムをクラッシュさせる仕様でした。たとえば次のコードは、55357(サロゲートの値であり単体では無効なコードポイント)を渡した時点でクラッシュします。

var string = ""
let codepoint: UInt32 = 55357 // this is invalid
let ucode = UnicodeScalar(codepoint) // Program crashes at this point.
string.append(ucode)

入力が信頼できる範囲にあると保証できる場面ならこの挙動でも構いませんが、外部から読み込んだデータなど、値が有効かどうか事前には分からない入力から UnicodeScalar を作りたい場面では使いづらく、利用側で毎回事前チェックを書かざるを得ませんでした。

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

整数値を受け取る UnicodeScalar の初期化子を failable 初期化子に変更し、無効なコードポイントが渡された場合は nil を返すようにします。

public struct UnicodeScalar {
    public init?(_ value: UInt32)
    public init?(_ value: UInt16)
    public init?(_ value: Int)
}

これにより、未知の入力からも安全に UnicodeScalar を構築できます。呼び出し側ではオプショナルバインディングなどで結果を受け、無効だった場合の処理を書き分けられます。

var string = ""
let codepoint: UInt32 = 55357 // this is invalid
if let ucode = UnicodeScalar(codepoint) {
    string.append(ucode)
} else {
    // do something else
}

既存コードへの影響として、これらの初期化子の戻り値がオプショナルになるため、呼び出し側ではアンラップが必要になります。値が常に有効だと分かっている場面では ! による強制アンラップで従来と同等の挙動(無効時にクラッシュ)を明示的に表現できます。