Swift Digest
SE-0137 | Swift Evolution

Avoiding Lock-In to Legacy Protocol Designs

Proposal
SE-0137
Authors
Dave Abrahams, Dmitri Gribenko
Review Manager
John McCall
Status
Implemented (Swift 3.0)

01 何が問題だったのか

Swift 3.0 は ABI 安定版ではないものの、「Swift 3.0 で書かれたコードが Swift 4.0 以降でも動き続ける」というソース互換性の約束があります。このため、標準ライブラリの多くのプロトコルは、Swift 3.0 の時点で事実上ロックインされてしまうという問題がありました。特に refinement の階層に現れるプロトコルは、将来も標準ライブラリのバイナリに残し続けなければ Swift 3 のコードを動かせなくなります。

そのままでは、Swift 3.0 に紛れ込んでしまったレガシーな設計や、今後設計を見直したいプロトコルまでもが、将来にわたって公開 API として維持される恐れがありました。そこで、Swift 3.0 の段階で「標準ライブラリの公開 API として残すべきではない」と判断されたプロトコルについて、あらかじめ deprecate したり、名前を整理したりしておく必要がありました。

具体的には次の 3 点が対象です。

  • Indexable 系のプロトコル(IndexableMutableIndexable など)は、当時の言語機能の制約を回避するために設けられた、標準ライブラリの実装詳細でした。利用者は対応する Collection 系プロトコル(たとえば MutableIndexable ではなく MutableCollection)を使うべきで、Indexable 系を直接触る理由はありません。
  • ExpressibleByStringInterpolation は、当時すでに設計上の問題や機能的な限界が指摘されていました。Swift 3.0 までに設計を作り直す時間はなく、さらに Comparable のように「現状の API が前方互換な形で維持可能」と判断できる見通しも立っていませんでした。
  • Streamable という名前は、OutputStreamTextOutputStream にリネームされたことで、混乱を招きやすく、かつ将来他の用途に再利用したい名前になっていました。

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

Swift 3.0 で「残すべきでない」と判断されたプロトコルについて、deprecation と改名を先回りで行うことで、将来の設計変更の余地を確保します。既存のコードはコンパイルが通らなくなることはなく、警告が出るだけです。

Indexable 系プロトコルの deprecation

IndexableMutableIndexableRangeReplaceableIndexableBidirectionalIndexableRandomAccessIndexable といった Indexable 系プロトコルは、「Swift 4 で削除される」というメッセージ付きで deprecate されます。利用者は対応する Collection 系プロトコルを使うように切り替える必要があります。

// 旧: 実装詳細の Indexable 系を直接使っていたコード
func f<C: MutableIndexable>(_ c: inout C) { /* ... */ }

// 新: 対応する Collection 系プロトコルを使う
func f<C: MutableCollection>(_ c: inout C) { /* ... */ }

ExpressibleByStringInterpolation の deprecation

ExpressibleByStringInterpolation は、「設計の変更が予定されている」というメッセージ付きで deprecate されます。Swift 3.0 時点では新しい設計が確定していないため、前方互換を保てるかどうかも判断できず、いったん deprecated 扱いにして将来の再設計の余地を残します。

これは「プロトコル自体がすぐに消える」ことを意味するものではなく、独自型で文字列補間リテラルに対応させること自体は引き続き可能ですが、この API に依存する新規コードには警告が出ます。

StreamableTextOutputStreamable への改名

Streamable プロトコルは TextOutputStreamable に改名され、Streamable は deprecated な typealias として残されます。OutputStreamTextOutputStream にリネームされたのに揃える形です。

// 旧
struct Foo: Streamable {
    func write<Target: OutputStream>(to target: inout Target) { /* ... */ }
}

// 新
struct Foo: TextOutputStreamable {
    func write<Target: TextOutputStream>(to target: inout Target) { /* ... */ }
}

Streamable という名前を使っているコードは、マイグレータによって自動的に TextOutputStreamable へ書き換えられます。

影響

この変更でコードがコンパイルできなくなったり、挙動が変わったりすることはありません。deprecation によって警告が出るだけで、Streamable のリネームは自動マイグレーションの対象ですが、それ以外の警告を解消するには手作業でコードを書き換える必要があります。