Allow trailing closures in guard conditions
01 何が問題だったのか
Swift の if / while / guard は同じ条件節(condition-clause)を共有していて、真偽値の条件、#available 節、let / case によるパターン束縛を並べて書けるようになっています。しかし、これらの条件節に書ける式には共通の制約として、トップレベルでのトレイリングクロージャが禁止 されていました。
if / while で禁止されている理由
if と while の場合、条件節のすぐ後ろには本体の { ... } が続きます。このため、条件式の末尾に { が現れると「トレイリングクロージャの始まり」と「本体ブロックの始まり」とをパーサーが区別できません。
if foo { // トレイリングクロージャの開始なのか、if 本体の開始なのか?
任意先読みや型チェックを絡めれば判別できるケースもありますが、コンパイラのアーキテクチャへの影響が大きいため、パーサーを単純に保つ方針として、if / while では条件節のトップレベルでのトレイリングクロージャを禁止しています。
guard では本来その制約が要らない
一方で guard は本体が else キーワードで明示的に区切られるため、条件節の直後に現れる { は常にトレイリングクロージャの始まりであって、本体ブロックではありません。つまり guard には if / while のような曖昧さはそもそも存在しません。
それにもかかわらず、guard でも条件節のトップレベルでのトレイリングクロージャは一律禁止されていました。たとえば次のようなコードはコンパイルが通りません。
guard let object = someSequence.findElement { $0.passesTest() } else {
return
}
これは歴史的な経緯によるもので、初期の設計では guard は else を使わない unless キーワードで表現されていました。その名残で、guard/else 構文に変更されたあとも条件節のトレイリングクロージャ禁止ルールだけが残ってしまっていた、という整理です。本提案は、この guard に限定された不要な制約を取り除こうとするものでした。
02 どのように解決されるのか
この提案は Rejected(却下) となりました。guard の条件節でも、トップレベルでのトレイリングクロージャは引き続き書けません。次のようなコードは、現在の Swift でもコンパイルエラーになります。
// NG: guard 条件節のトップレベルでトレイリングクロージャは使えない
guard let object = someSequence.findElement { $0.passesTest() } else {
return
}
提案されていた内容(却下されたもの)
提案は、guard の本体が else で明確に区切られていて曖昧さが無いことを根拠に、guard に限って条件節のトップレベルでのトレイリングクロージャを許そう、というものでした。仮に採択されていれば、上のコードはそのままコンパイルが通るようになっていたはずです。パーサーの変更自体はごく小さく、既存コードへの影響も「これまでエラーだったものが受け入れられるようになる」方向だけで、後方互換性の問題は生じない見込みでした。
却下の理由
Swift core team は、この変更によって guard だけが if / while と異なる挙動を持つことになり、3 つの条件付き構文の間に一貫性の乱れが生じる ことの方が、得られる利便性よりも大きなコストだと判断しました。if / while で同じ書き方ができないまま guard だけが特別扱いになると、学習時にも読み書きの際にも余計なルールを覚えることになります。
現在の Swift での書き方
guard の条件節で、末尾にクロージャを受け取るメソッド呼び出しを書きたい場合は、トレイリングクロージャではなく通常の引数として丸括弧の中にクロージャを書く 必要があります。
// OK: 引数リスト内のクロージャはトップレベルではないので使える
guard let object = someSequence.findElement(where: { $0.passesTest() }) else {
return
}
あるいは、条件部分をいったん let で取り出してから guard に渡す、という分け方もよく使われます。
let object = someSequence.findElement { $0.passesTest() }
guard let object else {
return
}
if / while / guard のいずれについても、「条件節のトップレベルに書けるトレイリングクロージャ」は現在も存在しません。条件式の中でどうしてもトレイリングクロージャ記法を使いたい場合は、式全体を括弧で包んでトップレベルから外す、という回避策も取れます。