Swift Digest
SE-0010 | Swift Evolution

Add StaticString.UnicodeScalarView

Proposal
SE-0010
Authors
Lily Ballard
Review Manager
Doug Gregor
Status
Rejected

01 何が問題だったのか

Swift 2.x 当時、StaticString はコンパイル時に静的に決まる文字列リテラルを表す型として用意されており、__FILE__(現在の #file)など一部のAPIが引数の型として受け取っていました。この提案は、その StaticString から部分文字列を取り出して、再び StaticString として扱う手段を追加しようとするものでした。

StaticString のままでは一部分を取り出せない

StaticString には、空文字列を作る引数なしの init() しか初期化手段がなく、既存の StaticString を加工して新しい StaticString を作る方法がありませんでした。たとえば __FILE__ からファイル名部分だけを抜き出したいとき、String に変換すれば部分文字列は取れますが、そうすると StaticString を受け取るAPIにはもう渡せません。StaticString を求めるAPIに「元の StaticString の一部」を渡したいだけでも、型として橋渡しできない、という穴がありました。

UnicodeScalar のシーケンスとしても扱いにくい

加えて、当時の StaticString は内部的にUTF-8バッファとしてしか触れず、UnicodeScalar の並びとして順に走査するような用途にも素直に対応していませんでした。文字列リテラル由来の静的な値に対して、スキャンや先頭一致の判定といった軽い処理をしたいケースでも、一度 String に持ち上げる必要があったのです。

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

この提案は Rejected(却下) となりました。したがって、StaticStringUnicodeScalarView や、そこから StaticString を再構築する初期化子は追加されていません。StaticString を加工したい場合は、現在も String に変換してから扱うのが基本です。

提案されていた内容(却下されたもの)

仮に採択されていれば、StaticStringunicodeScalars プロパティと、ネストした型 StaticString.UnicodeScalarView が追加される予定でした。UnicodeScalarViewCollectionType(現在の Collection に相当)に適合し、UnicodeScalar の列として順方向・逆方向にたどれるコレクションとして設計されていました。

あわせて StaticString に、UnicodeScalarView とそのスライスを受け取る2つの初期化子が追加されます。これにより、「UnicodeScalarView を取り出し → スライスを作り → StaticString に戻す」という流れで、StaticString の部分文字列を再び StaticString として扱えるようになる想定でした。

extension StaticString {
  public var unicodeScalars: UnicodeScalarView { get }

  public init(_: UnicodeScalarView)
  public init(_: Slice<UnicodeScalarView>)

  public struct UnicodeScalarView : CollectionType {
    public struct Index : BidirectionalIndexType, Comparable { /* ... */ }

    public var startIndex: Index { get }
    public var endIndex: Index { get }
    public var isEmpty: Bool { get }

    public subscript(position: Index) -> UnicodeScalar { get }
  }
}

使い方のイメージとしては、たとえば __FILE__ からファイル名だけを取り出して、引き続き StaticString として扱いたい、という場面が想定されていました。

// 仮に採択されていたらこのような書き方ができる想定だった
func logFileName(file: StaticString = __FILE__) {
  let scalars = file.unicodeScalars
  // '/' の後ろ側をスライスとして取り出す
  let afterSlash = scalars.split("/").last ?? Slice(scalars)
  let fileName = StaticString(afterSlash)
  // fileName は StaticString のまま、StaticString を要求する API へ渡せる
}

却下された理由

レビューでは、StaticString はそもそも「文字列リテラルから直接作られた、静的に確定した値」を表すための非常に狭い用途の型であり、そこに汎用のコレクションAPIを載せていくのは型のスコープを広げすぎる、という懸念が示されました。部分文字列を StaticString のまま扱いたい、という需要自体が限定的で、String に変換してから処理する回避策でほとんどのケースは足りる、という整理です。

StaticString のAPIを拡張するのであれば、個別の要望に個別に応える小さなAPIを足すのではなく、StaticString という型の位置付け全体を見直したうえで設計し直すべきだ、という判断がなされ、本提案は見送られました。

実務上のスタンス

現在のSwiftでも、StaticString から部分文字列を取り出して StaticString のまま扱う方法はありません。#file#function などから一部を切り出したい場合は、一度 String に変換してから扱うのが現状のプラクティスです。StaticString を要求するAPIには、もともとのリテラルをそのまま渡すのが基本的な使い方になります。