Swift Digest
SE-0007 | Swift Evolution

Remove C-style for-loops with conditions and incrementers

Proposal
SE-0007
Authors
Erica Sadun
Review Manager
Doug Gregor
Status
Implemented (Swift 3.0)

01 何が問題だったのか

初期のSwiftには、C言語から受け継いだ古典的な for ループ構文がありました。初期化・条件・更新の3つの式をセミコロンで区切って書くスタイルです。

for var i = 0; i < 10; i++ {
  print(i)
}

この構文はC系言語を知っている人にとっては馴染み深いものでしたが、Swiftの他の機能との重なりや言語の方向性を踏まえると、残しておく理由が薄い機能でした。

Swift的な代替手段がすでに揃っている

Swiftには for-inRangestride(from:to:by:) / stride(from:through:by:) といった、より簡潔で型安全なループ表現が用意されています。インデックスを明示的に扱いたい場面でも、C スタイルの for を使うよりこれらの方がSwiftらしく書けます。

// 0 から 9 まで
for i in 0..<10 {
  print(i)
}

// 0 から 10 未満を 2 刻み
for i in stride(from: 0, to: 10, by: 2) {
  print(i)
}

C スタイルの for はコレクションとも噛み合いが悪く、配列の要素を順にたどるような基本的な用途でも、インデックスを経由する冗長な書き方になりがちでした。

記述量・可読性の面で不利

C スタイルの for は、for-in と比べて書くべき量が多く、初期化・条件・更新の3つを1行にまとめるため式の密度も高くなります。C 系以外の言語から来た利用者にとっては、セミコロン区切りの宣言構文自体が学習コストになっていました。

削除される他の機能との連鎖

C スタイルの for は、別提案で削除が決まっていた ++ / -- 演算子(SE-0004)の主要な利用箇所でもありました。++ / -- をなくすなら、それらに強く依存する C スタイルの for も同時に整理する方が言語全体として筋が通ります。

実際にはあまり使われていない

Appleの Swift コードベースや、コミュニティの実プロダクトのコードベース、GitHub 上の Swift コードの調査からは、この構文が実際にはほとんど使われていないことが示唆されていました。使われている箇所の多くも、Swiftに入門したばかりの利用者のコードで、言語に慣れるにつれて自然に for-in などへ置き換わっていくパターンが多く見られました。

総じて、「もしこの機能が今なかったとして、これから Swift に追加したいか?」という問いに Yes と答えにくい機能であり、言語を簡潔に保つために削除候補として扱われました。

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

C スタイルの for ループを言語から取り除きます。Swift 2.2 でこの構文を非推奨(deprecated)とし、Fix-it で書き換えを補助したうえで、Swift 3 で完全に削除されました。

書き換え方

単純なカウントアップのループは、for-inRange を使う形にそのまま置き換えられます。

// Before:
for var i = 0; i < n; i++ {
  print(i)
}

// After:
for i in 0..<n {
  print(i)
}

刻み幅が 1 でない場合や、逆順にたどりたい場合は stride を使います。

// Before: 0, 2, 4, ..., 8
for var i = 0; i < 10; i += 2 {
  print(i)
}

// After:
for i in stride(from: 0, to: 10, by: 2) {
  print(i)
}

// Before: 10, 9, 8, ..., 1
for var i = 10; i > 0; i -= 1 {
  print(i)
}

// After:
for i in stride(from: 10, to: 0, by: -1) {
  print(i)
}

配列などコレクションをたどる場合は、インデックスを経由せず要素を直接取り出す方が素直に書けます。インデックスも一緒に欲しい場合は enumerated() を使います。

let xs = [10, 20, 30, 40, 50]

// Before:
for var i = 0; i < xs.count; i++ {
  print(xs[i])
}

// After: 要素を直接たどる
for x in xs {
  print(x)
}

// After: インデックスも必要な場合
for (i, x) in xs.enumerated() {
  print(i, x)
}

条件式が複雑で3式の for に収まりにくい場合や、ループ変数の型・更新方法が素直に Rangestride で表せない場合は、while ループで書き直すのが確実です。

// Before:
for var i = start; someCondition(i); i = nextIndex(i) {
  body(i)
}

// After:
var i = start
while someCondition(i) {
  body(i)
  i = nextIndex(i)
}

この変更で得られるもの

  • ループ表現が for-inwhile に集約され、言語の表層がひとつ小さくなります。
  • Swift 本来のコレクション指向のスタイル(for-in / Range / stride / enumerated())が素直な第一選択として前に出ます。
  • ++ / -- を削除する SE-0004 と足並みを揃え、削除後に残る書き方(+= 1 など)との整合も取りやすくなります。