The second review of SE-0478: File-level defaults (née "Default actor isolation typealias") begins now and runs through June 19, 2026.
After the first review was returned for revision, the proposal authors have substantially revised the feature to propose a new and more general mechanism for controlling these defaults.
Reviews are an important part of the Swift evolution process. All review feedback should be either on this forum thread or, if you would like to keep your feedback private, directly to the review manager. When contacting the review manager directly, please keep the proposal link at the top of the message.
What goes into a review?
The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift. When writing your review, here are some questions you might want to answer in your review:
What is your evaluation of the proposal?
Is the problem being addressed significant enough to warrant a change to Swift?
Does this proposal fit well with the feel and direction of Swift?
If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
How much effort did you put into your review? A glance, a quick reading, or an in-depth study?
More information about the Swift evolution process is available at
This looks like a good design for the feature. I especially like the use of the default keyword to avoid having to use a contextual keyword.
I’m sorry, this seems like a problem for linters, not language rules. Writing import in the middle of a file is ambiguous in precisely the same way (does the import affect the entire file, or just the declarations below it?), but we don’t have a rule forbidding it. I think Swift developers have internalized by now that Swift mostly doesn’t care about the order of declarations but that writing these sorts of file-wide meta-declarations first is good style. They don’t need the compiler to force them to do it.
Another issue with using this for macros is that most attributes are designed to “fail quietly” when they’re inferred from context (see e.g. non-ObjC-compatible members in an @objcMembers extension), but there isn’t an obvious way to do that for macros.
I can see the usefulness for @available, but the general use case is very iffy to me. Swift is already bad about local reasoning for code, so having to deal with file level defaults along with everything else makes a bad situation even worse. Many of these new implicit settings are designed to make things easier for beginners, but often end up making things harder since there's so much implicit behavior you have to learn. (@MainActor by default is especially bad at this.) Personally, I'll likely turn it off with a linter for most things.
Additionally, when this is added, for the love of god, please, please, please, please add this info to the appropriate tools (LSP, whatever Xcode uses) so this can be visualized in source editors. I should be able to hold (or toggle) a button and see the defaults that are currently active on the declarations I'm looking at.
In my experience, MainActor-by-default was an unmitigated disaster. Isolated conformances are a mess. The error messages you get in MainActor-by-default code don't help you diagnose the problems. You get just as many error messages as nonisolated-by-default code, so it doesn't make anything actually easier. And splitting of all Swift code into multiple dialects (default MainActor, NonisolatedNonsendingByDefault) has made it impossible to handle questions like "why doesn't my code compile" without needing information that the people asking such questions have no idea how to determine.
This proposal seeks to introduce two additional modes, MainActor-by-default-per-file (which does not behave as MainActor-by-default-per-module) and nonisolated-by-default-per-file (which does not behave as nonisolated-by-default). Even if we want file-level control of this (and I am extremely skeptical of that assertion; it seems to exacerbate every problem with the existing features), why wouldn't we want it to behave the same as the existing modes?
The spec doesn't seem to say what happens if you apply default @MainActor to a file in a MainActor-by-default module (which dialect do you get, or is it an error?) nor default nonisolated to a nonisolated-by-default module.
As with the previous compilerSettings [ ... ] proposal, I don't see a reason for this to be a keyword-based statement with novel syntax. Swift has a well-established idiom for special compile-time things: they start with #. That character stands out well at the beginning of a symbol, and is very easy to scan for. Parens should also be included, to align it with #available(...), #error(...), #selector(...), and so on. This feature should be spelled like:
In the absence of any specifically mentioned carveouts (which I don't believe there to be), the overarching principle applies that you get the same behavior as if you wrote @MainActor or nonisolated on the relevant top-level declaration in that file.
That consistency is why I think this particular proposal will improve rather than exacerbate the problem you point out: having a locally (i.e., per-file) visible indicator in source code of what the default is going to be means that users won't have to choose between sprinkling keywords everywhere (and potentially forgetting one) or relying on locally invisible dialect choices.
I also think the proposal's decision to make the behavior of default as consistent as possible regardless of what's being defaulted, and thus amenable to local reasoning (rather than inheriting idiosyncratic rules that drag along other flags or toggles, perpetuating the behaviors of locally invisible dialect choices), is the right decision here.
By that line of reasoning, shouldn't it be #import(...)?
I think it's good that the rules here are consistent enough that the feature feels "macro-like." But having the extra punctuation doesn't clarify much here to me: unlike availability or errors, I don't think there are compile-time versus runtime duals that need distinguishing.
+1 to this. Consider this example of how the proposed limitation still allows for confusing code:
import Foundation // should this import be allowed?
default @diagnose(ImportingModulesStartingWithTheLetterF, as: error)
Since I assume the diagnostic would apply to the whole file, this is really no different than allowing the file-level diagnostic to be placed after a regular declaration.
This kind of appeal to syntactic similarity is a bit of an oversimplification; it falls apart when you look beyond the punctuation.
Fundamentally, the majority of #-prefixed invocations are "expression-like", in the sense that they take something that looks like an argument list and then act like an expression (#selector, #file, #line, other custom macros, even #available if you stretch a bit†), a statement (#error, #warning), or a freestanding declaration (custom macros). Furthermore, all of these things are very narrowly scoped—they're either part of an expression or they're expanded at the exact point in the source code where they're located and don't impact anything around it.
The proposed feature is none of those things. The "parameters" to default are isolated modifiers or attributes, not terms that normally stand on their own. Furthermore, the feature impacts everything in the file, not just the local context around where the syntax is used.
I'm quite happy with the use of default here. It uses an existing keyword in a way that makes sense for the feature and doesn't require the trickiness of parsing a contextual keyword.
† #available looks like a Boolean expression but isn't, because it can't be combined with other expressions via operators like && and ||. It's a "condition list element", which means it can only be expressed in conjunctive form and separated from other conditions in control flow statements with a comma. It's also a run-time check, not compile-time.
Totally agreed. Really, most (if not all?) top-level declarations are allowed to follow their usage sites. We allow this:
let x: Foo = 5
// 10,000 lines of additional code
typealias Foo = Int
So I don't think we should make a special ordering rule for default. Developers who care about their code being readable will do the right thing, and tools like swift-format can enforce it.
Let's just make sure that there aren't any weird edge cases around ordering here. We have compiler bugs about the ordering of @preconcurrency imports with respect to other imports that re-export the same module, which means swift-format sometimes causes failures when reordering imports alphabetically. The test suite should include a bunch of different orders, even if they aren't something that a real developer would type, to make sure that they all behave identically so that we have confidence that defaults can be moved around by tooling.
Is there any chance we would want defaults to be overridable in the future as you proceed through the file? That would be one reason to pin them to the top for now.
I can’t imagine we’d want defaults to be able to affect parsing enough to make it a logical problem to find them later in the file, so that’s out, at least.
I would be happy if we didn't introduce the complexity of multiple sections of defaults in a single file. With it affecting the whole file, users are likely to put default at the top of the file as a matter of style, which makes it easy to scan for. If it was allowed to change throughout the file, that would require users to put it near the top for the first partition, which is nice, but then you lose the ability to look at any arbitrary declaration in the file and know which defaults apply to it because there could be any number of them between that declaration and the top of the file.
My gut feeling is that if you need multiple sections of defaults in the same file, any of the alternatives would be better than doing that:
Move each section of defaults to its own file
Drop default and just annotate the declarations directly
From the proposal
Specifying default @MainActor at the top of the file will instruct the compiler to use @MainActor as the default isolation for unspecified top-level declarations.
This is a different mechanism from SE-0466's -default-isolation. The file-level default isolation is placed on every legal top-level declaration, with no complex inference rules or adjustments.
[...]
Unlike -default-isolation , we intend file-level defaults to be usable for libraries and public APIs. For those users, silent carve-outs are especially unfortunate, requiring programmers using this feature to keep all the inference rules in mind. A reviewer cannot tell which declarations are exempt from the default without knowing every case, inspecting inferred types, or looking at emitted interface files.
I'm very happy to see that default @MainActor does not go through the heroics of complex inference rules. I particularly agree with the idea that if complex inference rules are used, then reviewers (and developers in general) need to know all the complex inference rules, and it makes it harder to understand the code at a casual glance.
In that case, I'll go back to my original position that we now have four modes, and that is three too many. I don't think there's a situation where "more modes" is better. We seem to be stuck with two. If this proposal was suggesting a different two, where one of them has advantages over the one it's replacing (the mode is syntactically evident and self-consistent) that'd be one thing. Seeking to add an additional two is simply exacerbating the problem.
tl;dr: If we're not moving to deprecate -default-isolation,
default @MainActor should behave exactly as if -default-isolation MainActor were specified for the file.
default nonisolated should behave exactly as if -default-isolation were not specified for the file.
If there is a concern that the behavior of those constructs is then unpredictable compared to default @available and default diagnose, then they should be spelled differently (and possibly split into a separate proposal). But the proposal is already inconsistent in that regard (each default applies only where applicable anyway), so I don't actually think this is a problem.
Yeah, I'm not sure I understand this point. It's precisely because the feature as proposed doesn't do anything more than handwritten @MainActor or nonisolated would that it doesn't add other modes.
If this feature behaved the way you describe, isolated conformances and @preconcurrency would be inferred on a per-file basis. That would be an additional mode (on a per-file basis! basically fractal patterning with modes!) that I'm not sure anybody wants.
At least then you'd have the option to say, "rather than use the per-module flag, I'll always use the per-file directive". And helping answer questions, you'd always have the option of saying "add this per-file directive and tell me what happens" (no change or perhaps error saying that's already set: that was already the module's mode; change: at least we now know what the module's mode was).
I don't really see why you think that it would add additional modes to do so? I'm explicitly suggesting that it puts each file into one of the two existing modes, the only difference to the status quo being that that's no longer necessarily consistent between files in a module. Are you thinking of intra-module interactions?.
If we're keeping the flags and adding the directives, there are, per-file, 6 modes for default isolation, and based on the proposal, I don't know what all of them do:
adds nonisolated to all top-level declarations that aren't explicitly marked for a global actor. Since this is as if the user typed it, it "wins" the competition with the flag, but doesn't necessarily cancel out all the behaviors of the flag?
-default-isolation nonisolated / unspecified
existing mode, the Swift default
Just adds @MainActor to all top-level declarations not already marked nonisolated or for a global actor.
Adds @MainActor to all top-level declarations not already marked nonisolated or for a global actor. Since this is as if the user typed it, it wins the competition with the flag and other isolation inference rules, but doesn't cancel out module-wide toggles that come with the -default-isolation flag.
Adds nonisolated to all top-level declarations that aren't explicitly marked for a global actor. Since this is as if the user typed it, it wins the competition with the flag and other isolation inference rules, but doesn't cancel out module-wide toggles that come with the -default-isolation flag.
-default-isolation nonisolated
existing mode (default)
Adds @MainActor to all top-level declarations not already marked nonisolated or for a global actor. Since this is as if the user typed it, it wins the competition with the flag and other isolation inference rules.
Adds nonisolated to all top-level declarations that aren't explicitly marked for a global actor. Since this is as if the user typed it, it wins the competition with the flag and other isolation inference rules.
I do have general hesitations about the value proposition of a feature like this. Something that I heard a long time ago that stuck with me is "Code is written once but read thousands of times," with the idea being that we should optimize for code readability.
If that's an agreeable premise, then following from that idea, we should consider how code is actually read. Rarely do we read files top-to-bottom; most of the time it's one random chunk that was changed in a PR, or you go-to-definition on a function that's being called in some code you're debugging. If something is no longer behaving as you'd expect, there's now a new place you have to look—a place that can be rather far from the code you're reading, I would argue—in order to identify the source of the problem.
Regardless of how many actual modes code can now exist in, this does introduce a new place people have to look to debug their code, a new bit of muscle memory people have to develop, and I kind of feel like that's not great.
It occurred to me as a shower thought that one future direction which could be a great use case for the general feature would be floating-point behavior.
For example, if default @DecimalRoundingMode(.down) could set all decimal floating-point arithmetic in the file to round down within the available precision. I think such choices are more appropriately inherent to the use site at compile time than to the thread at runtime, and this feature would allow a choice to be expressed in that way.
I choose Foundation.Decimal as the example deliberately because it prompts two open questions: (a) whether there’s a future where this feature could be used with arbitrary macros (as it would have to be for a non-stdlib type)—this question is already touched on, I believe, in the text; (b) whether decorating top-level declarations could be sufficient for such purposes (maybe so, if custom macro attributes could be recursively attached to inner lexical scopes? but that would imply a macro would have to be applied over the entirety of the file?).
Btw, was an alternative considered where these settings are set as the compilations flags per file (similar to how ‘-fno-objc-arc’ flags were set on obj-c files)? What are the disadvantages of such approach?