Swift Digest
SE-0478 | Swift Evolution

ファイル単位のデフォルト

File-level defaults

Proposal
SE-0478
Authors
Aviva Ruben, Holly Borla, Pavel Yaskevich
Review Manager
Steve Canon
Status
Active Review (June 5...19, 2026)

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

01 何が問題だったのか

Swift では、属性や修飾子をファイル内のすべてのトップレベル宣言に揃って付けたい場面がよくあります。たとえば次のようなケースです。

  • 警告制御を行う SE-0522@diagnose を、特定のファイルだけまとめて適用したい
  • プラットフォーム固有 API や並行処理と相性が悪い API を集めたファイルで、すべての宣言に同じ @available を付けたい
  • SE-0466 でモジュール単位のデフォルト isolation を MainActor にしているなかで、並行コードを集めたファイルだけ nonisolated をデフォルトにしたい

@diagnose はもともと宣言単位の属性で、@available も同様です。SE-0466 のデフォルト isolation も、モジュール単位の指定はあるもののファイル単位の指定はありません。そのため、いずれの場合も「ファイル内のすべての宣言に同じ属性・修飾子を書いて回る」か「ファイルを別モジュールに分割する」しか選択肢がありませんでした。

同じ属性をすべての宣言に繰り返し書くスタイルにはもう一つ問題があります。読み手はその属性を「常に付いているもの」として読み飛ばすようになるため、ある宣言だけ属性が抜けていたり微妙に内容が違っていたりしても気づきにくくなります。

ファイル単位で「このファイルではすべての宣言を nonisolated にする」「このファイルでは @available(*, noasync, ...) を全宣言のデフォルトにする」と一度宣言できれば十分なのに、そのための軽量な手段がありませんでした。

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

ファイルの先頭に default 宣言を書き、そのファイル全体に効くデフォルトの属性または修飾子を指定できるようにします。default キーワードのあとに属性または宣言修飾子を続けます。

// LegacyLogger.swift

default @available(*, deprecated, message: "this logging system uses global state")

// Implicitly @available(*, deprecated, message: "this logging system uses global state")
func log(message: String) { ... }

// Implicitly @available(*, deprecated, message: "this logging system uses global state")
func initLogging() { ... }

default で指定できるのは次の4つだけで、それ以外の属性・修飾子・式を default のあとに書くとエラーになります。

  • default @MainActor
  • default nonisolated
  • default @available(...)
  • default @diagnose(...)

default で書いた属性・修飾子は、対象のトップレベル宣言のそれぞれに直接書いたかのように扱われますが、宣言自身に明示的に書かれた属性のほうが優先されます。各属性・修飾子の伝播・推論ルールはこれまでと同じです。

書ける場所

default 宣言はトップレベルにしか書けません。関数や型の中に書くとエラーです。

func f() {
  default @MainActor // error
}

また、import 文を除くすべての宣言より前に置く必要があります。ファイルの途中に書くとエラーです。

default @available(swift, introduced: 5.9) // legal

import MyLibrary

default @diagnose(StrictMemorySafety, as: error) // legal

func foo() {}

default nonisolated // error

これは「default が宣言のあとに書かれていないか読み手が探さなくて済む」ようにするための制約です。

複数指定

同じファイルに default を複数書くこともできます。属性については各属性のセマンティクスに従って合成されます。修飾子(isolation)については、その修飾子自身のルールに従います。たとえば @MainActor @MainActor struct ...nonisolated @MainActor class ... が許されないのと同じ理由で、isolation のデフォルトを同一・矛盾するように重ねるとエラーです。

default @MainActor
default @MainActor // error
default nonisolated
default @available(swift, introduced: 5.1)
default @MainActor // error

Actor isolation のデフォルト

default @MainActor を書くと、そのファイル内の未指定のトップレベル宣言が暗黙的に @MainActor になります。モジュール側で -default-isolation nonisolated が指定されていても、ファイル単位のデフォルトが上書きします。

default @MainActor

// Implicitly '@MainActor'
struct S {}

// Implicitly '@MainActor'
extension S {
  // Implicitly the same as the extension, which is implicitly '@MainActor'
  func foo() {}
}

// still 'nonisolated'
nonisolated extension S {
  // Implicitly the same as the extension, which is explicitly 'nonisolated'
  func bar() {}
}

// still 'nonisolated'
nonisolated struct T {}

逆に default nonisolated を書けば、すべての未指定トップレベル宣言が nonisolated になります。これはモジュール全体を MainActor デフォルトにしているなかで、特定のファイルだけ並行処理向けに切り出したい場合に使えます。

typealiasimport のように isolation を持てない宣言、actor のようにすでに isolated な宣言には default の isolation は適用されません。

SE-0466 のモジュールデフォルトとの違い

SE-0466 の -default-isolation には、コードがコンパイルしやすくなるよう細かい推論ルールやカーブアウトが入っています(例: SendableMetatype 関連)。ファイル単位の default には、こうしたカーブアウトは入りません。default で指定したデフォルトは、適格なトップレベル宣言すべてにそのまま適用されます。

そのため、モジュール単位のデフォルトでは黙って外れるところでも、ファイル単位のデフォルトではエラーが出ます。

default @MainActor

// Implicitly '@MainActor'
class S { ... }

// Implicitly '@MainActor'
extension S: Hashable { ... } // error: Conformance of 'S' to protocol 'Hashable' crosses into main actor-isolated code and can cause data races

default nonisolated も、単に -default-isolation MainActor を打ち消すだけではなく、拡張対象が isolated でも上書きします。

// In MyLibrary

@MainActor
class S { ... }

// In MyClient compiled with -default-isolation MainActor
import MyLibrary

default nonisolated

// Implicitly nonisolated, even though S is isolated to @MainActor
extension S {
  // Accessing @MainActor isolated state from S would be an error
}

このように、default の isolation はあくまで「意図的にこのファイルではこれをデフォルトにする」という強い指定として扱われ、合わない宣言については個別に @MainActor extension S のように上書きするか、別ファイルに移すことになります。

スクリプトモード(トップレベルコード)でも default nonisolated は効きます。通常はトップレベルのグローバル変数は @MainActor デフォルトですが、default nonisolated を書くとそれが上書きされます。ただし SE-0412 の制約により、isolated でも immutable + Sendable でもないグローバル変数はエラーになります。

// script mode

default nonisolated

// implicitly nonisolated
var count = 0 // error, mutable global is neither isolated nor immutable+Sendable

// implicitly nonisolated
let limit = 100 // allowed

なお、-default-isolation が同時に有効化する InferIsolatedConformancesNoExplicitNonIsolated は、ファイル単位の default では有効化されません。これらはソース互換性に影響しうるため、ファイル単位で気軽に付け外しできる挙動とは合わないからです。

また、Swift 5 言語モードでは -default-isolation MainActor で推論された isolation が @preconcurrency 扱いになり、公開モジュールインターフェースにもそう記録されますが、default @MainActor ではこの挙動は引き継がれません。default で推論された @MainActor は明示的なアノテーションと同じ扱いです。

@diagnose のデフォルト

default @diagnose(...) を書くと、そのファイルのすべてのトップレベル宣言に @diagnose(...) を付けたのと同じ効果になります。SE-0522 の @diagnose は字句スコープに効くので、トップレベル宣言の内側のネストした宣言にも自然に波及します。

// Legacy.swift

default @diagnose(DeprecatedDeclaration, as: ignored, reason: "Maintaining backwards compatibility")

// Implicitly '@diagnose(DeprecatedDeclaration, as: ignored)'
func restoreLegacyAPI() {
  deprecatedHelper()
}

// Implicitly '@diagnose(DeprecatedDeclaration, as: ignored)'
extension Logger {
  func legacyFormat(_ s: String) -> String {
    oldStringFormatter(s)
  }
}

// explicit '@diagnose' takes precedence over the default
@diagnose(DeprecatedDeclaration, as: warning)
func migrationBridge() {
  anotherDeprecatedHelper()
}

default @diagnose(...) は複数書くこともでき、SE-0522 の「同じ宣言に複数の @diagnose を書いたときの後勝ちルール」が、各宣言に書かれた @diagnose より前にデフォルトを書いたものとして適用されます。

default @diagnose(DiagGroupID, as: warning)
default @diagnose(DiagGroupID, as: error)

// implicit @diagnose(DiagGroupID, as: warning)
// implicit @diagnose(DiagGroupID, as: error)
public func foo() // DiagGroupID is diagnosed as an error

// implicit @diagnose(DiagGroupID, as: warning)
// implicit @diagnose(DiagGroupID, as: error)
@diagnose(DiagGroupID, as: ignored)
public func bar() // DiagGroupID is ignored

@available のデフォルト

default @available(...) を書くと、そのファイルのすべてのトップレベル宣言に @available(...) を付けたのと同じ効果になります。

// LockedCollections.swift

default @available(*, noasync, message: "holding a lock across suspension may deadlock")

// Implicitly '@available(*, noasync, message: "holding a lock across suspension may deadlock")'
public final class LockedEventLog {
  public func lock() { ... }
  public func unlock() { ... }
  public func append(_ event: String) { ... }
  public func entries() -> [String] { ... }
}

// Implicitly '@available(*, noasync, message: "holding a lock across suspension may deadlock")'
public final class LockedCache<Key: Hashable, Value> {
  public func lock() { ... }
  public func unlock() { ... }
  public func get(_ key: Key) -> Value? { ... }
  public func set(_ key: Key, to value: Value) { ... }
}

default @available(...) も複数書け、@available を同じ宣言に複数書いたときと同じマージルールに従います。@available には少し癖のあるマージ動作があり(たとえば @available(iOS, unavailable) は明示しない限り Catalyst や visionOS にも適用されます)、それらは default で書いた場合もそのまま再現されます。

宣言側に書かれた @availabledefault よりも厳しい場合は、宣言側の制約が優先されます(@available は最も厳しいものが勝つため)。

default @available(macOS 12, *)

// Has default macOS 12 availability
public protocol Widget { ... }

// Has default macOS 12 availability
public struct BasicWidget: Widget { ... }

// Has explicit macOS 14 availability
@available(macOS 14, *)
public protocol AdvancedWidget { ... }

// Inherits macOS 14 availability from the extended protocol
extension AdvancedWidget { ... }

ネストしたメンバーや内部の型には、通常の @available 伝播ルールに従ってデフォルトの availability が引き継がれます。

公開 API への影響

default は宣言ごとに属性・修飾子を書くのと挙動が一致するため、公開 API でも使えます。ABI への影響も「同じ属性・修飾子を全宣言に書いた場合」と同じです。default を取り外して同じ ABI を保つには、対象の属性・修飾子を各宣言に明示的に書けば済みます。

ただし、Swift 5 言語モードで -default-isolation MainActor から default @MainActor に乗り換える場合、SE-0466 では暗黙的に付いていた @preconcurrency がモジュールインターフェースから消えるため、利用側でクロスアクター診断の扱いが変わる可能性があります。同じ ABI と挙動を保つには、対象の公開宣言に @preconcurrency を明示する必要があります。

現在のステータス

この提案は二度目のレビュー(Active Review、2026年6月5日〜19日)に入った段階です。実装は experimental feature flag DefaultIsolationPerFile の下で進められていますが、現状の実装はまだ using 構文(旧版)で出荷されており、今回の改訂で default キーワードに切り替えることが提案されています。

03 今後の見通し

default 宣言は将来的にさらに広げられるよう、汎用的なキーワードとして設計されています。以下は本提案の時点での構想で、実現を約束するものではありません。

任意のグローバルアクター

現状サポートされる isolation のデフォルトは default @MainActordefault nonisolated だけですが、将来は利用者が定義したグローバルアクターを書けるようにし、ファイル単位で「カスタムアクターをデフォルトにする」モードを表現できるよう拡張する可能性があります。

追加の属性・修飾子

default でサポートする属性・修飾子は今後の Proposal で増えていく可能性があります。一方で、コードの挙動を非局所的にする度合いが大きい属性・修飾子は、サポートを慎重に検討する必要があります。検討候補と、その懸念点は次の通りです。

  • @concurrent: トップレベルの async 関数にしか付けられず、型やエクステンションには付けられないため、メソッドへの適用ルールを別に決めるか、@concurrent 自体をすべてのトップレベル宣言で許容するように拡張する必要があります。字句スコープも持ちません。
  • @preconcurrency: 宣言ごとに明示することの意味があり、ファイル全体に広げると意図が伝わりにくくなる可能性があります。
  • @unsafe: ファイル全体に効かせると、ファイル内の一部の関数だけを読んでいる利用者から「unsafe な性質」が見えにくくなります。
  • アクセス制御: トップレベル宣言のアクセス制御は通常、その内側のメンバーには波及しません(ただし public extension のような例外もあります)。default 化すると、誤って API を public にしてしまったときに巻き戻すのが難しくなります。

新しい候補を追加する際は、その属性・修飾子が次の条件を満たすかを Swift Evolution の場で検討することが想定されています。

  • ほとんどのトップレベル宣言に一貫した挙動で適用できる
  • 今後新しい種類のトップレベル宣言に対しても対応関係が増えにくい
  • 何らかの字句スコープのルールを持つ
  • 一つのファイルでほとんどの宣言に書かれることが多い

マクロ属性のサポート

将来的に default @CustomAttrMacro のように、マクロ属性をファイル単位のデフォルトとして指定できるようにする構想があります。ただし、マクロが新しいメソッドやメンバーを生成するようなケースで「離れた場所での思わぬ作用」が起きやすく、慎重な設計が必要になります。

IDE / LSP サポート

default が効いた宣言の側にも、推論された属性・修飾子をインレイヒントとして表示できるようにする構想があります。これは default を使うコードの読み手に、それぞれの宣言にどんな属性が暗黙的に付いているかをすぐ伝えるためのものです。書き手側にとっても、自分が書いているコードに何が暗黙的に付いているかを忘れにくくする効果が期待されます。