この記事の要点
- Swift の新しいメジャーバージョンでは、ごくまれにソース互換性を壊す変更(source-breaking change)が取り込まれます。こうした変更はすぐには有効化されず、次のメジャーバージョンを待ちます。
- Swift 5.8 では、こうした「将来のメジャーバージョンで有効になる変更」を、機能ごと・ターゲットごとに前倒しで採用できる汎用的な仕組みとして upcoming feature flag が導入されました(SE-0362)。コンパイラフラグ
-enable-upcoming-feature <機能名>で個別に有効化します。 - 早めに採用すると、必要な修正の規模を自分のスケジュールで把握でき、新しい構文・挙動に慣れていけます。次のメジャーバージョンへの移行も滑らかになります。
- 有効化は Xcode のビルド設定(
Other Swift Flags)や Swift Package Manager のマニフェスト(SwiftSetting)で行います。コード側では#if hasFeature(...)で機能の有無に応じた分岐も書けます。
背景: ソース互換性と source-breaking change
Swift は新しいリリースのたびに機能を追加しますが、その際に重視されるのが ソース互換性(source compatibility) です。これは「既存の Swift コードが、新しいコンパイラでもそのままコンパイルでき、期待どおりに動作し続ける」という性質です。Swift プロジェクトは大規模なソース互換性テストスイートを維持し、変更がソース互換性を壊さないことを確認しています。
それでもごくまれに、ソース互換性を壊してでも取り込む価値があると判断される変更があります。こうした source-breaking な変更はすぐには有効化されず、次のメジャーバージョン(たとえば Swift 6)まで待つことになります。
例: Regex リテラル
source-breaking change の一例が、Swift 5.7 で導入された Regex リテラルの構文です(SE-0354)。多くのツールや言語の慣習にならって、パターンをスラッシュで囲む「素のスラッシュ」構文が望まれていました。
let regex = /[a-zA-Z_][0-9a-zA-Z_]*/
しかしこの構文は一部の既存コードを壊すため、Swift 5.7 では拡張区切り(#/ ... /#)のみがサポートされ、素のスラッシュ構文は Swift 6 まで持ち越されました。
let regex = #/[a-zA-Z_][0-9a-zA-Z_]*/#
一方で、Swift 6 を待たずに素のスラッシュ構文を使いたい開発者もいます。そこで Swift 5.7 では、これを前倒しで有効にする専用フラグ -enable-bare-slash-regex が追加されました。
upcoming feature flag という汎用的な仕組み
「将来の変更を早めに採用できる」のは良いことですが、機能ごとに専用のコンパイラフラグを増やし続けるのはスケールしません。そこで SE-0362(Swift 5.8 で実装)が、upcoming feature を有効化する汎用的な仕組みを定めました。
機能ごとに別々のフラグを作る代わりに、フラグは 1 つだけ用意し、その後ろに有効化したい機能名を続けます。
-enable-upcoming-feature SomeUpcomingFeature
たとえば素のスラッシュ構文の Regex リテラルなら次のようになります。
-enable-upcoming-feature BareSlashRegexLiterals
Swift Package Manager のマニフェストでは、SwiftSetting で指定します。
.enableUpcomingFeature("BareSlashRegexLiterals")
コード内で機能の有無を判定する
SE-0362 は、ある機能が有効かどうかを調べる hasFeature() というコンパイル条件も導入しました。これにより、機能が使えるときはそれを使い、使えないときは代替コードを使う、といった書き分けができます。
hasFeature() は Swift 5.8 で導入されたため、それより前のコンパイラは認識しません。使うときはコンパイラバージョンの判定と組み合わせる必要があります。
#if compiler(>=5.8)
#if hasFeature(BareSlashRegexLiterals)
let regex = /.../
#else
let regex = #/.../#
#endif
#else
let regex = try NSRegularExpression(pattern: "...")
#endif
upcoming feature flag を有効にする利点
upcoming feature を有効にする利点は大きく 2 つあります。
- 必要な修正にすぐ気づける。 コードに修正が必要かどうかが直ちにわかり、作業の規模を把握できます。今すぐ直すのか、スケジュールに合わせて後で直すのかを柔軟に選べます。
- 新しい機能をすぐ使い始められる。 以後に書くコードはすべて新しい挙動に沿ったものになり、次のメジャーバージョンへの移行が楽になります。新しい構文・挙動への慣れも早く身につきます。
有効化の方法
upcoming feature は、ターゲットごとにコンパイラフラグを設定して有効化します。複数の機能を有効にするには、機能ごとにフラグを繰り返します。これにより、ターゲット単位・機能単位という柔軟な粒度で採用できます。
Xcode の場合
Xcode プロジェクトでは、Other Swift Flags ビルド設定にフラグを追加します(プロジェクトナビゲータでプロジェクトを選択 → 対象ターゲットまたはプロジェクトを選択 → Build Settings タブ → スコープを All にして swift flags で検索 → Other Swift Flags に -enable-upcoming-feature フラグを追加)。
xcconfig 設定ファイルを使う場合は、バックスラッシュで複数行に分けると読みやすくなります。
OTHER_SWIFT_FLAGS = $(inherited) \
-enable-upcoming-feature ConciseMagicFile \
-enable-upcoming-feature BareSlashRegexLiterals \
-enable-upcoming-feature ExistentialAny
Swift Package Manager の場合
Swift パッケージでは、パッケージマニフェストのターゲットの swiftSettings 配列に指定します。
.target(name: "MyTarget",
dependencies: [.fancyLibrary],
swiftSettings:
[.enableUpcomingFeature("ConciseMagicFile"),
.enableUpcomingFeature("BareSlashRegexLiterals"),
.enableUpcomingFeature("ExistentialAny")])
あわせて、マニフェストの tools version を 5.8 以降に更新する必要があります。
// swift-tools-version: 5.8
upcoming feature flag の探し方
利用可能な upcoming feature flag は Swift Evolution Dashboard で探せます。upcoming feature flag を持つ Proposal だけに絞り込んだり、フラグ名で検索したりできます。各エントリは、そのフラグを定義している Swift Evolution の Proposal にリンクしており、フラグが有効化する変更の詳細は Proposal 本文(多くは Source Compatibility のセクション)に書かれています。
なお、Swift への変更の大半はソース互換性を保つため、upcoming feature flag を持ちません。
Swift 5.8 時点の upcoming feature flag
記事公開時点(Swift 5.8)で利用できた upcoming feature flag は次のとおりです。いずれも、ここで前倒し採用できる挙動は、次のメジャーバージョン(Swift 6)で既定になることが予定されているものです。
ConciseMagicFile(SE-0274: 簡潔なマジックファイル名):#fileリテラルがフルパスではなく<モジュール名>/<ファイル名>形式の文字列を生成します。ForwardTrailingClosures(SE-0286: 末尾クロージャに対する前方走査マッチング): 複数の末尾クロージャで呼び出すと曖昧になったり型チェックに失敗したりする既存メソッドについて、マッチングのルールを変更します。-
ExistentialAny(SE-0335: existentialanyを導入する): プロトコルを existential 型として使うときにanyキーワードを必須にします。protocol Drawable { } let drawable: any Drawable // 'any' キーワードが必要 -
BareSlashRegexLiterals(SE-0354: Regex リテラル): Regex リテラルで素のスラッシュを区切りに使えるようにします。let regex = /[a-zA-Z_][0-9a-zA-Z_]*/
今すぐ始めるには
upcoming feature を有効にすると、次のメジャーバージョンで入る変更を、機能ごと・ターゲットごとに自分のペースで取り込めます。まずはプロジェクトのテスト用ブランチを作り、1 つずつ・1 ターゲットずつ有効化していくのが低リスクな始め方です。チームで進める場合は、こうしたコード移行を計画し、共有しておく必要があります。
関連リンク
- SE-0362: upcoming language improvements の段階的採用 — upcoming feature flag と
hasFeature()を導入した Proposal - SE-0274: 簡潔なマジックファイル名
- SE-0286: 末尾クロージャに対する前方走査マッチング
- SE-0335: existential
anyを導入する - SE-0354: Regex リテラル
- Swift Evolution Dashboard — upcoming feature flag を検索・一覧できる公式ダッシュボード