Remove access modifiers from extensions
01 何が問題だったのか
Swift 2.x 当時、extension に付けるアクセス修飾子(public / internal / fileprivate / private)は、クラス・構造体・列挙型に付けるそれとは異なる、やや特殊なふるまいをしていました。この提案は、そのふるまいを整理し直すことを目的としていました。
extension のアクセス修飾子のふるまいが特殊
クラス・構造体・列挙型のアクセス修飾子は「その型自体の可視性」を表し、メンバーの可視性はメンバー側で個別に指定します。ところが extension に付けるアクセス修飾子は、その extension 自身の可視性というより 「その中に含まれるメンバーのデフォルトのアクセスレベル」 を設定するための道具として使われていました。
public struct D {}
// この extension 自体も private 扱いで、
// foo のデフォルトのアクセスレベルも private になる
private extension D {
// 修飾子を書かなければ暗黙的に private
func foo() {}
}
さらに、型に修飾子を付けなかった場合は、extension 側で修飾子を省略しても「拡張される型のアクセスレベルに合わせる」という挙動になっていました。
private struct E {}
extension E {
// 修飾子を書かなければ暗黙的に private
func foo() {}
}
extension に付けた修飾子は、メンバー側でそれより 低い 修飾子を書くことで上書きできます。つまり extension の修飾子は「メンバーが取り得る最大のアクセスレベル」かつ「省略時のデフォルト」を兼ねていました。
public struct F {}
internal extension F {
// private まで下げられるが public にはできない
private func foo() {}
}
protocol 準拠を付ける extension には修飾子を書けない
Swift 2.x では、type-inheritance-clause(: SomeProtocol の部分)を持つ extension にはアクセス修飾子を書くことが許されていませんでした。
public protocol SomeProtocol {}
// 'public' modifier cannot be used with
// extensions that declare protocol conformances
public extension A : SomeProtocol {}
このため、「ある型をプロトコルに適合させつつ、その準拠だけ可視性を絞る」といった書き分けができませんでした。
public なデフォルト実装の書き方が三通りある
プロトコルのデフォルト実装を extension で書くとき、extension の修飾子とメンバーの修飾子の組み合わせによって、同じ意味 の宣言が複数の綴りで書けてしまっていました。
public protocol G {
func foo()
}
// 書き方1: extension には付けず、メンバーに public を付ける
extension G {
public func foo() { /* implement */ }
}
// 書き方2: extension に public を付け、メンバーは省略する
public extension G {
func foo() { /* implement */ }
}
// 書き方3: 両方に public を付ける
public extension G {
public func foo() { /* implement */ }
}
いずれも公開 API としてのインポート結果は同じになるため、読み手はどの書き方が何を意味しているのかを判断するために extension のアクセス修飾子の特殊ルールを思い出す必要があり、学習コストが無駄にかかっていました。
要するに「extension のアクセス修飾子だけが、他のスコープ宣言とは別のルールで動いていて、しかも protocol 準拠との組み合わせに制約があり、同じ意味のコードの書き方が複数存在する」という、アクセス制御まわりの一貫性の低さが問題視されていました。
02 どのように解決されるのか
この提案は Rejected(却下) となりました。したがって、extension のアクセス修飾子のふるまいは Swift 3 以降も基本的に従来どおりで、この提案で示された改訂は取り込まれていません。
提案されていた内容(却下されたもの)
提案の骨子は次の3点でした。
- extension に付けた修飾子で「メンバーのデフォルトのアクセスレベル」を設定できる、というふるまいを やめる。メンバーのアクセスレベルは、拡張される型自身に宣言したときと同じデフォルト(典型的には
internal)に従う。 type-inheritance-clauseを持つ extension(extension SomeType : SomeProtocolの形)にも アクセス修飾子を書けるようにする。- extension のアクセス修飾子は、拡張される型のアクセスレベルと、適合させるプロトコルのアクセスレベルを尊重した値だけを取れるようにする(例:
publicな型とpublicなプロトコルの組ならpublic extensionになる、など)。
採択されていれば、次のように「プロトコル準拠を付ける extension に修飾子を書く」ことが可能になり、
internal protocol H {
func foo()
}
public struct J {}
// 準拠そのものは internal のままで、
// foo だけ public として公開する、といった書き分けができるようになる予定だった
public extension J : H {
public func foo() {}
// extension のアクセスレベルをデフォルトに使わないため、
// moo は(型が public なので)暗黙的に internal
func moo() {}
}
public なプロトコルのデフォルト実装の「正しい一通りの書き方」は、次のように extension とメンバーの双方に public を付ける形に限定される予定でした。
public extension G {
public func foo() { /* implement */ }
}
これは Swift 2.x の挙動に対しては破壊的変更であり、既存コードはマイグレーションツールで機械的に書き換えられる想定でした。たとえば public な型を拡張する修飾子なしの extension には、public extension を明示的に付ける、といった変更が入ることになります。
public struct AA {}
- extension AA {
+ public extension AA {
func member1() {}
public func member2() {}
private func member3() {}
}
却下された理由
レビューの結論としては、「extension のアクセス修飾子が “メンバーのデフォルトをまとめて設定する” 役割を担っているのは便利であり、それを廃止してしまうと、同じ可視性のメンバーをまとめて書きたいケースで毎回メンバー側に修飾子を書く必要が出てくる」といった点が重視されました。結果として、extension のアクセス修飾子まわりの規則は以下のような従来の挙動のまま残っています。
- extension に付けた修飾子は、その中のメンバーのデフォルトのアクセスレベルとして働く。
- メンバーは extension の修飾子より低いアクセスレベルに個別に下げられるが、上げることはできない。
- 拡張される型自体の修飾子が extension の取り得る上限を決める。
同じ時期に議論されていた関連提案(SE-0117 によるアクセス制御まわりの整理や、SE-0025 で導入された fileprivate / private の区別など)によって、extension まわりの可視性の表現力は別の形で補われていったこともあり、本提案の方向性はそのままの形では採用されませんでした。
実務上のスタンス
extension のアクセス修飾子の挙動は今もやや特殊に見えるポイントですが、次のように押さえておけば実用上は困りません。
- 同じアクセスレベルのメンバーをまとめたいときは、extension にそのレベルの修飾子を付けて中のメンバーは素の宣言にする、という書き方が一番簡潔です。
- 公開したいメンバーだけを選んで見せたい場合は、extension 側ではなくメンバー側で
publicを明示するほうが意図が伝わりやすいです。 - プロトコル準拠を付ける extension に修飾子を書くと現在もエラーになるため、そのようなケースでは extension を分けて書くのが基本形になります。