Swift Digest
Blog | Swift.org Blog

upcoming feature flag を使う

Using Upcoming Feature Flags

このダイジェストはClaude Opus 4.7 / 4.8によって生成されたものです(License)。原文はこちら

この記事の要点

背景: ソース互換性と 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 つあります。

  1. 必要な修正にすぐ気づける。 コードに修正が必要かどうかが直ちにわかり、作業の規模を把握できます。今すぐ直すのか、スケジュールに合わせて後で直すのかを柔軟に選べます。
  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)で既定になることが予定されているものです。

今すぐ始めるには

upcoming feature を有効にすると、次のメジャーバージョンで入る変更を、機能ごと・ターゲットごとに自分のペースで取り込めます。まずはプロジェクトのテスト用ブランチを作り、1 つずつ・1 ターゲットずつ有効化していくのが低リスクな始め方です。チームで進める場合は、こうしたコード移行を計画し、共有しておく必要があります。

関連リンク