Nonisolated async function in swift 6.2

Hi,

Video

If you want the function to stay on whatever actor it was called on, you can use the nonisolated keyword.

Confusion

  • However based on my testing the nonisolated async function doesn't stay on the same actor (main actor).
  • nonisolated async function compute() doesn't run on the main thread (because MainActor.assertIsolated("Not isolated") causes a crash).
  • Am I missing something?

Questions:

  1. Based on my interpretation what was stated in the video I was thinking that if already on the main actor and a non-isolated async function is invoked it would continue to run the main actor, but it doesn't seem to be the case. Am I missing something?
  2. How to know if a async function is running on main thread or background thread?
    • Is it based on MainActor.assertIsolated("Not isolated")?

Project

Environment:

  • Xcode 26.0 beta (17A5241e)
  • Toolchain: Swift 6.2 Development Snapshot 2025-06-14 (a)

Build Setting

  • Default Actor Isolation: Main Actor
  • Swift language version: 6

Code

Since the Default actor isolation is Main Actor, all run on Main Actor by default.

import Foundation
import Combine

// My assumption this would be Main actor because Default Actor Isolation: is Main Actor
@Observable
class Service {

    // I thought this would also be Main actor based on what is mentioned in the video
    // I am just saying based on by calling `MainActor.assertIsolated("Not isolated")`
    nonisolated
    func compute() async {
        // MainActor.assertIsolated("Not isolated") 
        // When MainActor.assertIsolated is uncommented would crash implying 
        // it is not running on the main thread
        for index in 0..<1000000 {
            print("index: \(index)")
        }
    }
}

It's a new "default" behavior that is being talked to about there.

To opt into it you have to either state nonisolated(nonsending) explicitly:

or, turn on this new opt-in mode that changes nonisolated to give this behavior by default by:

  • enabling "nonisolated(nonsending) by default" setting in Xcode (or the Approachable Concurrency setting, which implies the prior one),
  • or using the command line -enable-upcoming-feature NonisolatedNonsendingByDefault,
  • or in SwiftPM:
.enableUpcomingFeature("NonisolatedNonsendingByDefault")

You can read more about it here: swift/userdocs/diagnostics/nonisolated-nonsending-by-default.md at 32ec5a61b726ffbea62c5879e67a7396674d571c ยท swiftlang/swift ยท GitHub

This mode will eventually become the new default mode, but for now it is opt-in. We think this behavior is what nonisolated async functions always should have been doing, but we need to be careful about rolling out the behavior change, thus staging it in this way.

4 Likes

@ktoso Thanks a lot!!! I was breaking my head over it.

I have a few questions:

  1. In future once this feature goes live I suppose only we don't need to mention nonisolated(nonsending) instead could only mention nonisolated? For now we need to mention as nonisolated(nonsending)?

  2. So is the following correct?

  • @concurrent will change it to background

  • nonisolated(nonsending) will continue on the same as the actor's executor

  • @MainActor will change it to main actor.

  • Actually seems simpler to understand concurrency with the above 3 rules. Not sure if I over simplified them and missed any cases.

  1. Does that mean in different versions of Swift things would behave differently?

Well the "NonisolatedNonsendingByDefault" upcoming feature "is live" in the sense that you can opt into it.

There might be a future in which we make this the default, but it would be tied to a language mode. There's no concrete promise so far when or if that will be flipped to be the default though. But yeah that's the general direction.

this means "hop off calling actor executor; if there is a task executor, use that; if not, use the default global concurrent executor".

yes

yes

Yeah; so that's the "future state of the world". Until then we sadly have to deal with nonisolated and nonisolated(nonsending) but the latter will disapear in use in the long term as people enable the upcoming feature IMHO.

4 Likes

@ktoso Thank you so much for patiently explaining it!!!

1 Like

Note that this flag is automatically enabled when you enable the "Approachable Concurrency" build setting, which says this:

Enables upcoming features that aim to provide a more approachable path to Swift Concurrency:

  • DisableOutwardActorInference
  • GlobalActorIsolatedTypesUsability
  • InferIsolatedConformances
  • InferSendableFromCaptures
  • NonisolatedNonsendingByDefault

Most of the demos about the new concurrency features in that video are assuming that you have enabled that build setting.

5 Likes

Thanks a lot @bjhomer, turning on "Approachable Concurrency automatically sets all of the flags under "Swift Compiler Upcoming features" which includes nonisolated (nonsending) by default

The video mentions it at the end but didn't realize it.

Thanks a lot @Douglas_Gregor for this video, really helped me understand concurrency and made it more accessible to me.

Thanks a lot to the Swift community for these new Swift concurrency features makes it so much better!!