Adjusting inout Declarations for Type Decoration
01 何が問題だったのか
Swift 2 では、関数の引数を inout として宣言するときに、inout キーワードは次のように引数ラベル側(コロンの左側)に書く形になっていました。
func foo(inout x: Int) {
x += 1
}
しかし inout が修飾しているのは本来「引数の型」であって「引数ラベル」ではありません。この位置のずれは、いくつかの具体的な不便さと紛らわしさを生んでいました。
関数型の表記と整合しない
Swiftでは関数型を書くときに引数ラベルは省略できます。inout がラベル側の要素として扱われていると、関数型の中で inout をどこに置くべきかが自然に定まりません。ラベル側に残すと、ラベルを省略した型注記と整合しないことになります。
// 宣言側: inout がラベル寄りに見える
func increment(inout x: Int) { ... }
// 関数型として書きたい形:
// (inout Int) -> Void
// ただし「inout は本当はラベルではなく型にかかっている」という説明が必要
inout を型側の修飾子として扱えれば、(inout Int) -> Void のような型表記に素直に繋がります。
inOut のような引数ラベルと見分けがつかない
inout を引数ラベル側に書く構文では、引数ラベルとして別の単語を付けた場合と見た目が非常に近くなります。
func foo(inOut x: T) // 引数ラベル inOut を持つ普通の関数。型は (T) -> Void
func foo(inout x: T) // inout 引数を持つ関数。型は (inout T) -> Void
大文字の O が1文字あるかどうかで意味がまったく変わる、という状況は読み手に優しくありません。
inout だけが引数ラベルとして使えない
Swift 3 では、ほぼすべてのキーワードを引数ラベルとして使えるようになっています(SE-0001)。唯一の例外が inout で、これは inout がラベル位置に現れる特別なキーワードとして予約されていたためです。inout を型側に移すことができれば、この不揃いもなくなり、inout を引数ラベルとして自由に使えるようになります。
他言語との対応も取りづらい
Rust などの言語で借用(borrowing)を扱う修飾子は、いずれも型側に付く形をしています。Swiftでも将来的に類似の機能を検討する余地があることを考えると、inout を型側の修飾子として位置付けておく方が将来の拡張と整合的でした。
02 どのように解決されるのか
inout キーワードを引数ラベル側から型側へ、すなわちコロンの右側に移動します。inout は「引数の型に対する修飾子」として扱われ、型注記の一部として書きます。
// Before (Swift 2):
func foo(inout x: Int) {
x += 1
}
// After (Swift 3):
func foo(x: inout Int) {
x += 1
}
呼び出し側の書き方(& を付けて渡す)は従来どおりで、変更はありません。
var n = 0
foo(x: &n)
print(n) // 1
関数型の表記が素直になる
inout が型側の修飾子になったことで、関数型の中でも自然に位置付けられるようになります。
// (inout Int) -> Void のような型表記がそのまま意味を持つ
let f: (inout Int) -> Void = { x in x += 1 }
var n = 0
f(&n)
print(n) // 1
宣言での書き方(x: inout Int)と、型としての書き方((inout Int) -> Void)が一貫した位置に inout を置く形に揃います。
inout を引数ラベルとして使えるようになる
inout がラベル位置の予約語ではなくなるため、引数ラベルとして inout を使うことも可能になります。
func connect(inout socket: Socket) { ... }
// 呼び出し側: connect(inout: someSocket)
これにより、SE-0001 で導入された「キーワードを引数ラベルとして使える」というルールに inout も揃い、言語全体の一貫性が改善されます。
移行
Swift 2 のコードでは、既存の inout 引数宣言をコロンの右側に機械的に移し替えるだけで対応できます。Swift 3 のマイグレータがこの書き換えを自動で行います。