結論から言うと、もともと異なる2通りの動作をしていたものが1つの表記にまとめられており、Swiftがその違いを表面化させたのです。some View、any Error、some Collection。以前はプロトコル名をそのまま型の位置に書けましたが、現在はanyを付けないと警告やエラーになります。何が変わったのでしょうか。
要するに、ジェネリック編で予告した静的ポリモーフィズムと動的ポリモーフィズムの違いです。中級シリーズ第3回では、someとanyを整理します。
問題の起点 — プロトコルは型の位置で2つの顔を持つ
プロトコルは本来、制約を表すための言語です。Comparableに準拠する型のように、資格を表現します。しかし、プロトコル名を変数や戻り値の型に置くと、その性格が変わります。
let shapes: [Shape] = [Circle(), Square(), Triangle()]
この配列には異なる型が混在しています。これを可能にするため、コンパイラーは各値を箱に入れます。「Shapeに準拠する何か」と書かれた箱、これがexistential typeです。箱の中の具体的な型はランタイムまで分からず、メソッド呼び出しも箱を開けて実装を探す間接呼び出しになります。
問題は、この箱が無料ではないことです。サイズの異なる値を同じ箱に入れるため、専用のexistential containerが必要になり、大きな値はヒープに置いてポインターだけを格納します。呼び出しはwitness tableという関数一覧を経由する動的ディスパッチとなり、インライン化や特殊化など、コンパイラー最適化の多くが妨げられます。機能上の制約もあります。箱の中の具体的な型が消去されるため、2つのShapeが同じ型かどうかを判断しにくくなります。
しかし以前の構文では、このコストが見えませんでした。func draw(shape: Shape)と書くと箱が作られますが、func draw<S: Shape>(shape: S)と書けば箱なしでジェネリックとして動作します。見た目が似た2つのコードで性能特性がまったく異なるのに、構文がその違いを隠していたのです。Swift Evolution提案SE-0335がanyキーワードを導入した理由はまさにこれです。箱が作られる場所には、箱だと書いておこう。第1回で扱った「コストを隠さない」という思想が、構文の改定につながった例です。
some — 箱なしで隠す
anyが何でも入る箱なら、someは反対方向の道具です。some Shapeは具体的な型が1つに決まっているものの、その名前は明かさないという意味です。そのため不透明型(opaque type)と呼ばれます。
func makeShape() -> some Shape {
Circle(radius: 10) // 常に Circle 1つの型だけを返す
}
呼び出し側はCircleという名前を知りませんが、コンパイラーは知っています。だから箱も間接呼び出しもありません。静的ディスパッチと最適化がそのまま利用でき、実質的にはジェネリックと同じように扱われます。代償は柔軟性です。some Shapeを返す関数は、すべてのreturn経路で同じ具体的な型を返さなければなりません。条件によってCircleまたはSquareを返すとコンパイルエラーになります。
SwiftUIのvar body: some Viewは、この構文の代表的な用途です。bodyが実際に返す型はVStack<TupleView<(Text, Image)>>のような怪物ですが、これをシグネチャに書くことはできませんし、書きたいとも思いません。some Viewは名前だけを隠し、具体的な型はコンパイラーが把握したままにすることでこの問題を解決します。性能を犠牲にせずに済むのです。Progressive Disclosure編で「初心者が知らなくてもよい複雑さ」の例として見た構文の正体は、これです。
パラメーター位置のsomeも知っておくと便利です。func draw(shape: some Shape)はfunc draw<S: Shape>(shape: S)の短縮表記です(SE-0341)。型パラメーター名を本体で使わない場合に、ジェネリックを軽く書くための構文です。
選択基準 — 基本はsome、anyは理由があるとき
2つのキーワードの違いを表にまとめると、someはコンパイル時に1つの型が確定(静的ディスパッチ、最適化可能、型の関係を維持)、anyはランタイムに何でも可能(動的ディスパッチ、箱のコスト、型消去)です。
実務上の基準は明確です。基本はsome(またはジェネリック)とし、複数の型を本当に混在させる理由がある場合だけanyを使います。
anyが妥当なのは、おおむね3か所です。1つ目は異種コレクションです。[any Shape]のように異なる型を1つの配列に入れるには、箱が必要です。2つ目は型がランタイムに決まる保存プロパティです。設定に応じて異なる実装を差し込むvar strategy: any PaymentStrategyのような場所で、Strategy Patternや依存性注入のプロトコルプロパティの多くが該当します。3つ目は条件によって戻り値の型が変わる関数です。someでは許可されないケースです。
言い換えると、関数パラメーターでこのプロトコルに準拠する任意の型を受け取りたいだけなら、答えはsomeです。呼び出しごとに型は1つに決まるからです。Swiftチームのガイドも同じ方向を示しています。コレクションに混在させるか保存する必要があるときだけanyに昇格させる、ということです。
性能差は誇張する必要も、無視する必要もありません。UIイベントを数回処理するコードでは、anyのコストは意味を持ちません。しかし毎秒数万回実行されるループ内では、箱のコストと最適化の妨げが測定可能な差を生みます。いつものように基準は測定であり、デフォルトをsomeにすれば、そもそも測定が必要な場面を減らせます。
エラーメッセージを読み解く — 「anyを付けてください」が伝えること
この区別が分かると、これまで暗記してやり過ごしていたコンパイラーのメッセージも読み解けます。
「Use of protocol ‘X’ as a type must be written ‘any X’」は、プロトコルを型の位置に置くと箱が生まれる事実を、構文として認めなさいという要求です。機械的にanyを付ける前に、本当に箱が必要な場所か(someやジェネリックで済まないか)を一度考えるのが、このエラーへの正しい向き合い方です。
「Protocol ‘X’ can only be used as a generic constraint」は、以前のSwiftでassociatedtypeやSelfを持つプロトコルを型の位置に書いたときに出た有名なエラーです。箱に入れようとしても関連型が分からず、箱の規格を決められないという意味でした。現在はSE-0309やprimary associated typesなどによりかなり許可されていますが、この話は関連型そのものを扱わなければ正しく説明できません。次回のテーマです。
まとめ
- プロトコルを型の位置に置くとexistential type(箱)が作られ、動的ディスパッチ、コンテナのコスト、型消去が伴います。anyは、その箱に付ける正直なラベルです。
- someは逆に、具体的な型を1つに決めたまま、名前だけを隠します。箱がないため性能はジェネリックと同等で、SwiftUIのsome Viewが代表例です。
- 基本はsome(ジェネリック)とし、anyは異種コレクション、ランタイムで決まる保存、条件付き戻り値のように、複数の型が本当に混在する場所だけで使います。
- コンパイラーがanyを求めるのは「箱のコストを認識せよ」という合図です。無条件に付ける前に、someで済む場所か確認します。
次回は予告どおりassociatedtypeです。「generic constraint」エラーの根、プロトコルが型の空欄を含む意味、そしてprimary associated typesまで整理します。

![[Swift中級 #3] someとany、existentialのコストのカバー画像](/assets/images/posts/a880d464-6c55-4a06-bf69-b6f45434a102/1.jpg)