Swift Digest
SE-0009 | Swift Evolution

Require self for accessing instance members

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

01 何が問題だったのか

Swift 2.x 当時、インスタンスメンバー(プロパティやメソッド)にアクセスする際、エスケープするクロージャの中では self. を明示する必要がある一方、メソッド本体の直下ではそれを省略できるという非対称な仕様になっていました。この提案は、クロージャの内外を問わず、すべてのインスタンスメンバーアクセスで self. を必須にしようとするものでした。

インスタンスメンバーとローカル変数が見分けにくい

self. を省略できるせいで、コード上ではインスタンスプロパティなのかローカル変数なのか、インスタンスメソッドなのかローカル関数(あるいはクロージャ)なのかが見た目から判断できません。この曖昧さはとくに、読み手がコードを追いかける場面で認知コストを高めます。

意図しないメンバーを参照してしまうバグ

ローカル変数のつもりで使った名前が、実はスーパークラス由来のプロパティと衝突しており、コメントアウトやリファクタリングをきっかけに静かにそちらを参照してしまう、というバグが起こり得ました。提案では次のような例が挙げられています。

class MyViewController: UIViewController {
  @IBOutlet var button: UIButton!
  var name: String = "David"

  func updateButton() {
    // var title = "Hello \(name)"
    // 上の行をコメントアウトしたのに、以下の title を消し忘れた。
    // title はコンパイルエラーにならず、UIViewController の title プロパティを指してしまう。
    button.setTitle(title, forState: .Normal)
    button.setTitleColor(UIColor.blackColor(), forState: .Normal)
  }
}

self. を必須にしていれば、ローカル変数として宣言されていない title を裸で書いた時点でコンパイルエラーとなり、この種の取り違えを早期に検出できます。

クロージャでの self が持つ意味の希薄化

現行仕様ではクロージャ内でのみ self. が必須になるため、「self. がある = キャプチャに注意すべき文脈」というシグナルとして読めそうにも見えます。しかし実際には、エスケープするクロージャでは強制、非エスケープなクロージャでは任意、という扱いの差があり、self. の有無だけでは循環参照の危険を判断できません。提案は、クロージャでの self. を「キャプチャの警告」としてではなく、「インスタンスメンバーであることの明示」として一貫して扱うべきだ、と主張していました。

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

この提案は Rejected(却下) となりました。したがって、Swiftでは引き続きインスタンスメンバーへのアクセスで self. を省略できます(クロージャ内でのキャプチャが必要な文脈でのみ self. が求められる、という従来の挙動が維持されます)。

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

仮に採択されていれば、次のようなコードは Swift 3 以降でコンパイルエラーになる予定でした。Swift 2.x の段階では警告とFix-Itで段階的に移行し、Swift 3 でエラーへ昇格させる、という2段構えが提案されていました。

class Person {
  var name: String = "David"

  func foo() {
    print("Hello \(name)") // エラーになる予定だった
  }

  func bar() {
    foo() // エラーになる予定だった
  }
}

正しくするには次のように、すべてのインスタンスメンバーアクセスに self. を付ける必要がありました。

class Person {
  var name: String = "David"

  func foo() {
    print("Hello \(self.name)")
  }

  func bar() {
    self.foo()
  }
}

却下された理由

Swift の言語設計方針として、self. を常に要求することはSwiftが大事にしている簡潔さと噛み合わないと判断されました。インスタンスメンバーとローカル変数の取り違えは確かに起こり得ますが、IDEのシンタックスハイライトや、ローカル変数と衝突したときの警告など、言語構文を変えずに解決できる余地のある問題だと整理されています。クロージャ内で self. が必須になっているのは、「キャプチャが発生する文脈であることを読み手・書き手の双方に意識させる」という別の目的が中心であり、そのシグナルを薄めたくないという判断もありました。

実務上のスタンス

self. を書くかどうかはスタイルの選択として残ります。コードベースの規約として常に self. を付けることを選ぶチームもありますが、言語としては強制されません。インスタンスメンバーとローカル変数の取り違えを避けたい場合は、引数名とプロパティ名をあえて別にする、命名規約で区別する、といったコード側の工夫で対処するのが現状のプラクティスです。