Remove explicit use of let from Function Parameters
01 何が問題だったのか
Swiftでは、関数の引数はもともとイミュータブルなローカル定数として扱われます。それにもかかわらず、初期のSwiftでは引数名の前に明示的に let を書くことが許されていました。
func foo(let x: Int) {
// x は let を書いても書かなくても同じくイミュータブル
}
let を付けても意味が変わらない
この let は付けても付けなくても挙動は同じで、単に冗長な修飾子でしかありません。引数のミュータビリティを制御する手段としての役割を持たない以上、構文として残しておくメリットはほぼなく、読み手に「書かれている let には何か意味があるのだろうか」という無用の疑問を抱かせる要因になっていました。
var 引数の廃止で整合性が崩れる
SE-0003 によって引数宣言の var が禁止された結果、「引数はイミュータブルに固定する」という方針がはっきりしました。その一方で、同じく意味を持たない let だけが引数位置に書けるという非対称な状況が残り、言語として一貫性を欠いた状態になっていました。
引数ラベルとして let を使えない
SE-0001 は、ほぼすべてのキーワードを引数ラベルとして使えるようにする変更です。ただし当初は inout / var / let の3つだけが例外として除外されていました。その後 SE-0031 で inout が型修飾子側に移り、SE-0003 で var 引数が廃止されたことで、inout と var は引数ラベルとして使える見通しが立ちました。最後に残ったのが、冗長な let 引数のためだけに引数ラベルとしての利用が妨げられている let です。
02 どのように解決されるのか
関数・メソッド・イニシャライザ・クロージャなどの引数宣言で、引数名の前に let を明示することをコンパイルエラーにします。引数はこれまでどおり常にイミュータブルな定数として扱われるため、let を単に取り除くだけで同じ意味になります。
// Before: Swift 2 まで
func foo(let x: Int) {
// ...
}
// After: Swift 3 以降
func foo(x: Int) {
// ...
}
移行時の注意点
すでに引数に let を付けて書く慣習はほとんど広まっていなかったため、影響を受けるコードは限定的です。ただし、let を消し忘れて残しておくと、構文上は「let という外部引数ラベルを持つ引数 x」として解釈されてしまい、呼び出し側のラベルも変わってしまう点に注意が必要です。移行の際は、引数位置に書かれた let を単純に削除します。
引数ラベルとして let が使えるようになる
この変更によって、SE-0001 で制限されていた最後のキーワードである let も引数ラベルとして利用できるようになります。先行して inout と var が解放されており、これで例外なくすべてのキーワードを引数ラベルに使える状態が整います。
func copy(from source: Source, let destination: inout Destination) {
// ここでの let は引数ラベル。引数そのものは inout で書き戻される
}
これで、let 引数という冗長な構文を捨てる代わりに、let というラベル位置を自由に使えるようになり、引数宣言の文法全体がよりシンプルかつ一貫性のあるものになります。