Swift Digest
SE-0159 | Swift Evolution

Fix Private Access Levels

Proposal
SE-0159
Authors
David Hart
Review Manager
Doug Gregor
Status
Rejected

01 何が問題だったのか

Swift 2 までは、private はファイル単位のアクセス制御を表す修飾子でした。Swift 3 では SE-0025 により private の意味が「宣言されたスコープ内からのみアクセス可能」という、より狭いスコープベースのものに変更され、従来のファイルスコープの意味を表すために新しく fileprivate キーワードが導入されました。この提案は、その変更によって生じた問題を挙げ、private を Swift 2 のファイルベースの意味に戻し、fileprivate を非推奨化することを主張するものでした。

private がエクステンションと相性が悪い

Swift では同じファイル内で型の宣言を複数のエクステンションに分けて書くのが一般的なスタイルです。しかし、Swift 3 の private はエクステンションをまたぐと参照できなくなってしまいます。

struct Foo {
    private var value: Int = 0
}

extension Foo {
    func printValue() {
        print(value) // error: 'value' is inaccessible due to 'private' protection level
    }
}

同じファイル内・同じ型のエクステンションから使えない private は、ファイル内でコードを整理するためにエクステンションを多用するスタイルと噛み合いません。結果として、ちょっとしたプロパティやメソッドを隠したいだけなのに、常に fileprivate を選ぶか、エクステンションを諦めるか、という判断を強いられることになります。

「ソフトデフォルト」としての private に求められる性質

ファイル内で実装の詳細を隠したいときに最初に手が伸びる修飾子、という意味で private は事実上の「ソフトデフォルト」です。ソフトデフォルトは、Swift に馴染みのあるイディオム(同一ファイル内でのエクステンション活用)とうまく共存することが期待されます。スコープベースの private はその期待を裏切り、より自然な振る舞いを持つ方には fileprivate という長く扱いづらい名前が付いているため、読みやすさ・書きやすさの両面でコミュニティに不満が残っていました。

スコープベースのアクセス制御は本当に必要か

提案者は、privatefileprivate を分けることで得られる情報量(「同一ファイル内の他の型・エクステンションにすら見せたくない実装詳細である」)は、2 つのよく似たアクセスレベルを言語に抱え続ける認知コストに見合わない、と主張しました。将来的にサブモジュールなどの新しい可視性の仕組みを検討する余地を残すためにも、スコープベースのアクセス制御はいったん取り除いたほうがよい、という立場です。

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

この提案は Rejected(却下) となりました。したがって Swift では引き続き、private はスコープベース(宣言されたスコープ内のみ可視)、fileprivate はファイルベース(同一ファイル内のみ可視)という SE-0025 で導入された 2 段階のアクセス制御が維持されています。

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

採択されていれば、Swift 4 モードでは次のように変更される予定でした。

  • private の意味を Swift 2 と同じ「同一ファイル内からアクセス可能」に戻す。
  • fileprivate キーワードを非推奨化し、マイグレータが既存の fileprivate をすべて private に置き換える。
  • Swift 3 互換モードでは従来通り、privatefileprivate は SE-0025 の意味のまま維持する。

この変更の副作用として、同一ファイル内の別スコープに同じシグネチャの private 宣言が複数ある場合、Swift 4 モードでは再宣言エラーになる予定でした。たとえば次のコードは Swift 3 互換モードではコンパイルできますが、採択されていた Swift 4 モードでは Invalid redeclaration of 'bar()' エラーになるはずでした。

struct Foo {
    private func bar() {}
}

extension Foo {
    private func bar() {} // error: Invalid redeclaration of 'bar()'
}

却下された理由

レビューを経て、コアチームはこの提案を退けました。SE-0025 のスコープベースの private は意図的な設計であり、ファイル内で他の型やエクステンションにも見せたくない本当に限定的な実装詳細を表現する手段として価値がある、という整理です。一度導入した private の意味を元に戻すことは、コードベースに対する影響が大きい割に、得られるメリットは「ソフトデフォルトとしての使い勝手」というスタイル上の好みに寄っており、言語仕様を再度揺らすほどの必然性は認められませんでした。

実務上のスタンス

現在の Swift では、使い分けの目安は次のようになります。

  • 同じファイル内のエクステンションや他の型とも共有したい実装詳細には fileprivate を使う。
  • その宣言のあるスコープ(型本体・エクステンション本体など)の中だけで閉じた実装詳細には private を使う。

同じファイル内で型をエクステンションに分割するスタイルを好む場合、主要な内部状態や補助メソッドには fileprivate を選ぶと扱いやすくなります。一方で、本当にスコープ外には見せたくないヘルパーは private で明示することで、意図を型の読み手に伝えられます。