# Why is a Task closure defined in an Actor's method nonisolated?

**URL:** <https://forums.swift.org/t/why-is-a-task-closure-defined-in-an-actors-method-nonisolated/84967>\
**Category:** Using Swift\
**Tags:** concurrency\
**Created:** [February 25, 2026, 3:32am UTC](https://forums.swift.org/t/why-is-a-task-closure-defined-in-an-actors-method-nonisolated/84967 "2026-02-25T03:32:28Z")\
**Posts on this page:** 1\
**Showing post:** 7

<div class="post-metadata">

**Author:** ![NotTheNHK](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/notthenhk/32/43199_2.png) [@NotTheNHK](https://forums.swift.org/u/NotTheNHK)\
**Post date:** [February 25, 2026, 2:42pm UTC](https://forums.swift.org/t/why-is-a-task-closure-defined-in-an-actors-method-nonisolated/84967/7 "2026-02-25T14:42:09Z")

</div>

Mr. Wu already gave an excellent answer. However, I would like to elaborate further on why I think this is actually consistent (at least until recently, with the introduction of `nonisolated(nonsending)` ) with closure isolation rules in general. As you probably know, closures formed within an isolated context that have a notion of isolation (i.e., async closures) are `nonisolated` by default (except when the isolation is derived from a global actor):

```swift
func noIso() async {
    let c = { () async -> Void in 
        print("noIso", #isolation as (any Actor)?)
    }

    await c() // nil
}

@MainActor func isoToMain() async {
    let c = { () async -> Void in 
        print("isoToMain", #isolation as (any Actor)?)
    }

    await c() // MainActor
}

func isoToSomething(_ iso: isolated (any Actor)?) async {
    let c = { () async -> Void in 
        print("isoToSomething", #isolation as (any Actor)?)
    }

    await c() // nil
}

```

As with `Task`, you can capture the context’s isolation to isolate the closure like this:

```swift
 func captureIsoInClosure(_ iso: isolated (any Actor)?) async {
    let c = { () async -> Void in 
        _ = iso
        print("captureIsoInClosure", #isolation as (any Actor)?)
    }

    await c() // iso isolated
}

```

However, you need to capture the isolation directly, as aliasing [doesn’t work](https://swift.godbolt.org/z/bxaE91c8r):

```swift
func failedToCaptureIsoInClosure(_ iso: isolated (any Actor)?) async {
    let c = { [iso] () async -> Void in
        print("failedToCaptureIsoInClosure", #isolation as (any Actor)?)
    }

    await c() // nil
}

```

The difference between “normal closures” and “Task closures” is that the latter allows you to isolate even `@Sendable`closures. An `@Sendable` closure normally represents a `nonisolated` task, i.e., the closure executes on the GCE:

```swift
actor A {
    func doSomething() {}

    func f() {
        let c = { @Sendable () async -> Void in 
            self.doSomething() // cannot be called from outside of its isolation
        }

        Task {
            self.doSomething() // Okay
        }
    }
}

```

Personally, I think this is expected behavior if you’re aware of the general rules of closure isolation. However, I also think it is overall terribly confusing and needs further improvement. SE-0461 was already a step in the right direction, as it got rid of SE-0338 (which in hindsight was a rather poorly thought-out change, in my opinion).

As for your other question, I think all four methods, that I can come-up with, have their place: `#isolation`, `assertIsolated`/`preconditionIsolated`, wrapping partial task in print statements, and examining the SIL.

P.S. You might find the Godbolt examples in this comment interesting: [[Pre-Pitch]: updating `with{Checked|Unsafe}Continuation` to support typed throws (and perhaps nonisolated(nonsending)) - #8 by NotTheNHK](https://forums.swift.org/t/pre-pitch-updating-with-checked-unsafe-continuation-to-support-typed-throws-and-perhaps-nonisolated-nonsending/84770/8)

---

_[View the full topic](https://forums.swift.org/t/why-is-a-task-closure-defined-in-an-actors-method-nonisolated/84967)._
