SE-0461 keeps ordinary async calls on the caller's actor
Swift has accepted caller-actor execution for nonisolated async functions. The design changes where isolation boundaries arise and retains explicit concurrent execution for work that should leave an actor.
Making a method asynchronous can change whether an actor may pass its own state to it. Under Swift's existing rules, a nonisolated synchronous method runs on the caller's executor, while a nonisolated asynchronous method leaves the actor. That departure introduces a possible concurrent access to values the actor still owns. A method that was acceptable before adding async can consequently produce a data-race diagnostic afterward.
SE-0461, written by Holly Borla and John McCall, changes that default. On April 5, the Language Steering Group accepted the proposal with modifications after a focused re-review. Nonisolated async functions would normally run on the caller's actor. An explicit @concurrent annotation preserves the behavior that leaves the actor.
I find this a useful correction to the relationship between Swift's value model and its concurrency rules. Code that retains access to the same state does not necessarily need a new concurrency boundary merely because it can suspend. That judgment still leaves a separate question about which computations should occupy an actor.
The earlier rule addressed actor overhang
The existing behavior came from SE-0338. That proposal defined non-actor-isolated async functions as running on a generic executor associated with no actor. It addressed uncertainty about code that continued running on an actor after that actor's isolated state was no longer relevant.
That unwanted continuation can serialize work that does not need the actor. A long computation can prevent other tasks from making progress there. On the main actor, such work can interfere with responsiveness. Leaving the actor at an ordinary nonisolated async call gave the programmer a predictable way to avoid retaining that execution context.
The decision also affected sendability. Once the callee leaves the actor, another task can execute on the actor while the asynchronous operation remains active elsewhere. If both can reach the same non-Sendable object, the actor's serialization no longer protects all access to that object. The diagnostic follows from the execution rule.
SE-0461 accepts that history and changes the preferred default. Its motivation describes the old rule as prioritizing main-actor responsiveness at the expense of usability. The new design makes ordinary async calls preserve the caller's actor and asks code that should leave it to say so explicitly.
Suspension and isolation answer different questions
An async declaration permits suspension. It does not by itself mean that a function must run concurrently with the caller's actor. The proposed default keeps execution on that actor while allowing the function to await other operations according to their own isolation requirements.
SE-0461 illustrates the problem with an actor holding a non-Sendable reference. Its synchronous method call is acceptable because execution remains serialized on the actor. Calling a nonisolated async method on the same reference currently leaves that serialization and can fail checking. Under the proposed caller-actor behavior, that call no longer introduces the same boundary.
The new explicit spelling is nonisolated(nonsending). The argument describes the absence of an implicit send of argument and result values across an isolation boundary at the call. It does not grant arbitrary access to some concrete actor's stored properties. The function remains nonisolated in its declaration while its execution context can follow its caller.
For example, the following illustrates the accepted design. It is proposal syntax, not a claim that this acceptance installs new behavior in an existing toolchain:
final class Counter {
var value = 0
nonisolated(nonsending)
func increment() async {
value += 1
}
}
actor Owner {
private let counter = Counter()
func increment() async {
await counter.increment()
}
}
The important relationship is that Owner retains its counter while the method call continues on the owner's actor. The design does not require the counter type to claim that arbitrary concurrent access to its mutable property is safe. It avoids introducing that concurrent access through this call.
Explicit concurrent execution retains the boundary
@concurrent states that an async function switches off an actor to run. A caller cannot then pass actor-owned non-Sendable state under the assumption that the actor still serializes its use. The proposal applies sendability checking at that boundary, including its rules for values in disconnected regions.
This is useful for work whose execution should not occupy the actor. Moving computation away from the main actor remains possible, but the declaration expresses that intent. Making this choice explicit helps distinguish an operation that can suspend from an operation whose execution must be independent of the caller's actor.
The final acceptance includes a small but relevant change. @concurrent itself implies nonisolated, so an explicit nonisolated modifier is optional alongside it. The focused re-review records Nickolas Pokhylets suggesting that simplification and Holly Borla agreeing with it. Different spellings for the two behaviors made that implication less ambiguous.
@concurrent does not grant permission to access actor-isolated state. In an actor declaration, an annotated method still cannot directly use the actor's protected mutable properties as though it were running on that actor. The annotation establishes a departure from that context, which is precisely why the state restrictions matter.
The transitional spelling has a specific purpose
The first review used a different naming scheme. The March 28 decision reverted the concurrent spelling to @concurrent and sent the explicit caller-actor spelling into focused re-review. That review ran through April 4.
The steering group did not require symmetrical names for the two behaviors. It regarded the caller-actor spelling as transitional because that behavior is the intended default after adoption. @concurrent has a continuing purpose as an explicit request to leave the actor. The final choice consequently favors describing the argument-transfer rule in nonisolated(nonsending).
The March decision also removed an unconditional warning for declarations that spelled neither behavior. IDE information and migration tooling would help explain and preserve intent without requiring pervasive edits merely to silence that warning. This matters because the proposal changes the interpretation of source that can remain syntactically valid.
Adoption changes execution semantics
The proposed default is gated behind the NonisolatedNonsendingByDefault upcoming feature. Without adopting it, ordinary nonisolated async functions retain the existing behavior. Explicit spellings allow code to state a consistent intent while projects transition between settings.
This creates an intermediate period in which the source word nonisolated is insufficient to determine an async function's execution behavior without knowing the module's settings. The proposal discusses surfacing inferred behavior through SourceKit inspection. That information is useful because a performance-sensitive call can change execution context without requiring an obvious source rewrite.
I prefer explicit concurrent execution for a computation whose responsiveness depends on leaving the main actor. That is a judgment about the work the function performs, not a reason to give every asynchronous helper a concurrency boundary. The proposed default makes these two concerns easier to express independently.
There is also a binary-interface consequence. Caller-actor execution requires passing an implicit actor parameter. Changing an existing exported function to this behavior therefore has ABI implications. SE-0461 discusses possible compatibility transformations, but an accepted execution model should not be mistaken for a declaration that every existing binary can adopt it unchanged.
Following the caller also has to survive calls to other isolated functions. The proposal describes a helper that temporarily calls a main-actor function and then resumes on its original caller actor. Its implicit actor argument supplies the context needed for that return. Simply continuing on whichever executor happened to run the last operation would not provide the same guarantee. The design records the relationship to the caller so that a later suspension or nested call does not silently change which actor protects the remaining work.
Higher-order helpers explain the broader motivation
The proposal also considers async helpers that acquire a resource, call a supplied async body, and then clean up. Keeping the helper on the caller's actor does not solve the problem if the body function type still implies leaving that actor. The arguments and results passed through the body need a coherent isolation model as well.
That example explains why the change concerns function values alongside declarations. A useful default must survive passing a function as an argument. Otherwise an ordinary refactoring into a scoped helper can reintroduce sendability restrictions that direct calls avoided.
Acceptance establishes the design for that more consistent model. Implementations still need to preserve explicit actor transitions and diagnose unsafe transfers. The practical distinction is whether an async call continues under the caller's serialization or deliberately permits execution concurrent with that actor. SE-0461 makes the latter choice explicit while staging the changed default behind an adoption setting.