Swift Digest
SE-0028 | Swift Evolution

Modernizing Swift’s Debugging Identifiers

Proposal
SE-0028
Authors
Erica Sadun
Review Manager
Chris Lattner
Status
Implemented (Swift 2.2)

01 何が問題だったのか

Swift には、ソースコード中の現在位置やバイナリ情報を参照するために、__FILE__ / __LINE__ / __COLUMN__ / __FUNCTION__ / __DSO_HANDLE__ という組み込み識別子が用意されていました。最初の4つは現在位置に対応する文字列リテラルや整数リテラルに展開され、__DSO_HANDLE__ は現在の動的共有オブジェクト(.dylib / .so)への UnsafePointer を返します。ログ出力やエラーコンテキストの記録などで便利に使われてきた機能です。

func log(_ message: String,
         file: String = __FILE__,
         line: Int = __LINE__,
         function: String = __FUNCTION__) {
  print("[\(file):\(line) \(function)] \(message)")
}

Cから受け継いだ「叫ぶスネークケース」

これらの識別子の構文は、C のプリプロセッサマクロ __FILE__ / __LINE__ をそのまま踏襲したものです。Swift 自体はプリプロセッサを持たず実装の仕組みも異なりますが、見た目だけが C のマクロそっくりのまま残っていました。全大文字+アンダースコア区切りのいわゆる “screaming snake case” は、Swift の他の識別子の命名と調和せず、言語の表層を不揃いにする要因になっていました。

# で始まる他のコンパイラ機能と不整合

一方で、同じく「コンパイラに特別扱いされる式」を表す構文として、Swift にはすでに #available があり、SE-0022 で #selector(...) の採用も決まっていました。# 接頭辞は「ここでコンパイラによる置き換えを呼び出す」ことを示す目印として機能しつつあり、新旧の書き方が言語内に混在している状態でした。将来のマクロシステムを見据えたときにも、ソース位置系の識別子だけが旧来の大文字マクロ風のまま取り残されているのは望ましくありません。

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

既存のソース位置系識別子を、# 接頭辞の小文字表記にリネームします。機能面の挙動は従来のままで、表記だけを Swift らしい形に揃える変更です。

__FILE__ #file
__LINE__ #line
__COLUMN__ #column
__FUNCTION__ #function
__DSO_HANDLE__ #dsohandle

__FUNCTION__ はいったん削除候補でしたが、レビュー中に #function として残すことが決まりました。

使い方

通常の式の中では、その式が書かれた位置に展開されます。デフォルト引数に書くと、呼び出し元の位置に展開される、という “マジック” な挙動は従来どおり維持されます。これにより、ログ関数の呼び出し元を自動的に記録する、というよくあるイディオムがそのまま書けます。

func log(_ message: String,
         file: String = #file,
         line: Int = #line,
         function: String = #function) {
  print("[\(file):\(line) \(function)] \(message)")
}

log("hello")  // 呼び出し元のファイル名・行・関数名が自動的に入る

#dsohandle も同様に、現在の動的共有オブジェクトへの UnsafePointer をそのまま返します。

#line のコンパイラディレクティブとの両立

Swift にはもともと、ソース中の行番号を差し替えるためのディレクティブとして #line が存在していました。今回の提案で #line は「式としては現在行番号」「行頭の最初のトークンとして現れたときはディレクティブ」という二重の役割を持つことになります。コアチームはこの「行頭に現れたら特別扱い」というホワイトスペース依存の扱いを一時的なものと位置づけており、後続の議論でディレクティブ側をよりふさわしい名前に改める方針が示されています(実際に後の提案で #sourceLocation に置き換えられています)。

スコープ外になったもの

元提案では、モジュール・型・関数などを一括でまとめた完全修飾シンボルを返す #symbol の追加も検討されていましたが、これは別提案として切り出すことがレビュー時に決まっています。今回のスコープはあくまで既存識別子のリネームと、それに付随する挙動の整理に限定されます。

移行

旧来の __FILE__ などの識別子は、移行しやすさのために当面は併存するかたちで残され、新しい #file などへ誘導されます。既存コードは機械的な置換だけで新形式に追従できます。

// Before
let here = "\(__FILE__):\(__LINE__) in \(__FUNCTION__)"

// After
let here = "\(#file):\(#line) in \(#function)"

この変更により、Swift におけるコンパイラ置換の入り口は # 接頭辞に統一され、#available / #selector / #file などが同じ語彙として扱えるようになりました。