Swift Digest
SE-0478 | Swift Evolution

Default actor isolation typealias

Proposal
SE-0478
Authors
Holly Borla
Review Manager
Steve Canon
Status
Returned for revision

01 何が問題だったのか

SE-0466 で、モジュール単位でデフォルトの actor isolation を MainActor にするか nonisolated にするかを切り替えられるようになりました。これにより「基本はシングルスレッド、必要なところだけ並行にする」というスタイルのモジュールを自然に書けるようになっています。

しかし実際のコードでは、並行処理に関係する宣言だけを一つのファイルや少数のファイルにまとめることがよくあります。たとえばモジュール全体を MainActor デフォルトにしていても、あるファイルのなかの型や関数はまとめて nonisolated にしたい、といったケースです。

現状の選択肢は次のどちらかで、どちらも煩雑です。

  • そのファイルのすべての宣言に個別に nonisolated を書く(あるいは逆に @MainActor を付けて回る)
  • モジュールを分割して、別のターゲットに切り出す

ファイル単位で「このファイルは nonisolated がデフォルト」「このファイルは @MainActor がデフォルト」と宣言できれば十分なのに、そのための軽量な手段がありませんでした。

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

ソースファイルのトップレベルに DefaultIsolation という名前の private な typealias を書くことで、そのファイルだけのデフォルト isolation を指定できるようにします。基となる型として MainActornonisolated のどちらかを指定します。

MainActor を指定すれば、そのファイル内の宣言は暗黙的に @MainActor-isolated になります。

// main.swift

private typealias DefaultIsolation = MainActor

// 暗黙的に @MainActor
var global = 0

// 暗黙的に @MainActor
func main() { ... }

main()

nonisolated を指定すれば、そのファイル内の宣言は暗黙的に nonisolated になります。モジュール全体が MainActor デフォルトになっているときに、特定のファイルだけ nonisolated に寄せたい場合に使います。

// Point.swift

private typealias DefaultIsolation = nonisolated

// 暗黙的に nonisolated
struct Point {
    var x: Int
    var y: Int
}

適用条件

DefaultIsolation typealias は、次の条件をすべて満たしたときだけデフォルト isolation の決定に使われます。

  • トップレベルに書かれていること
  • アクセスレベルが private または fileprivate であること(internal 以上は不可。ファイル単位の設定であって、モジュール全体の設定ではないため)
  • 基となる型が MainActor または nonisolated のいずれかであること

条件を満たさない DefaultIsolation typealias を書くこと自体は可能ですが、その場合はデフォルト isolation の決定には使われず、コンパイラが警告でその理由を通知します。

@globalActor
actor CustomGlobalActor {
    static let shared = CustomGlobalActor()
}

private typealias DefaultIsolation = CustomGlobalActor // warning: not used for default actor isolation

nonisolated を型として書くための仕掛け

nonisolated は本来キーワードであり、そのままでは typealias の右辺に書けません。これを可能にするため、Concurrency ライブラリに nonisolated という名前の typealias が追加されます。

public typealias nonisolated = Never

nonisolated は文脈依存キーワードなので、識別子としても使える点を利用しています。この typealias はデフォルト isolation の指定以外の用途を持たず、DefaultIsolation に書くときは基となる型がちょうど nonisolated である必要があります(private typealias DefaultIsolation = Never のように Never を直接書くのは不可です)。

SE-0466 との関係

モジュール単位の設定(SE-0466 で導入された -default-isolation フラグや SwiftSetting.defaultIsolation)はそのまま残り、このファイル単位の設定はそれを上書きする形で動きます。たとえばモジュールを MainActor デフォルトにしつつ、並行処理をまとめたファイルだけ private typealias DefaultIsolation = nonisolated と書く、といった使い分けができます。

現在のステータス

この提案は現時点では Returned for revision の状態で、実装は experimental feature flag DefaultIsolationTypealias として提供されています。仕様は今後の改訂で変わる可能性があります。

将来への見通し

nonisolated typealias が手に入ったことで、パッケージマニフェスト API も nil 渡し(nilnonisolated を意味する)から、より自然な形へ整えていける可能性があります。仕様書では次のような形が Future Directions として挙げられています(本提案のスコープ外で、実現を約束するものではありません)。

SwiftSetting.defaultIsolation(nonisolated.self)

さらに、メタタイプに .self を付けずに書けるようにすることで、次のような記法に揃える方向も検討されています。

SwiftSetting.defaultIsolation(nonisolated)
SwiftSetting.defaultIsolation(MainActor)