# Potential false positive \`sending '...' risks causing data races\`

**URL:** <https://forums.swift.org/t/potential-false-positive-sending-risks-causing-data-races/78859>\
**Category:** Using Swift\
**Tags:** concurrency, strict-concurrency\
**Created:** [March 26, 2025, 6:19pm UTC](https://forums.swift.org/t/potential-false-positive-sending-risks-causing-data-races/78859 "2025-03-26T18:19:38Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![bobko](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/bobko/32/37854_2.png) [@bobko](https://forums.swift.org/u/bobko)\
**Post date:** [March 26, 2025, 6:19pm UTC](https://forums.swift.org/t/potential-false-positive-sending-risks-causing-data-races/78859/1 "2025-03-26T18:19:38Z")

</div>

I can't wrap my head around the following example:

```swift
class NonSendable {}

@MainActor
func main1() async {
    let ns = NonSendable()
    await foo(ns)
    await foo(ns)
}

@MainActor
func main2() async {
    let ns = NonSendable()
    await { await foo(ns) }() // error: sending 'ns' risks causing data races
    await runBlock { await foo(ns) } // error: sending 'ns' risks causing data races
}

func foo(_ a: Any) async {}

func runBlock(isolation: isolated (any Actor)? = #isolation, _ block: () async -> ()) async {
    await block()
}

```

`main1` is green but `main2` is red. I want to create a "transparent" `runBlock` function that just calls its closure, but I have no luck because of the error.

If I drop `@MainActor` from `main2`, then the diagnostic disappears. I use Swift 6.0.3.

Apparently, Swift doesn't have problems with sending `ns` from `main2` to the closure. It has problems with sending `ns` from the closure to `await foo`. But if it's the case then `main1` shouldn't work, should it?

Right now, I tend to think that it's a false positive diagnostic, but I may be missing something

---

<div class="post-metadata">

**Author:** ![YOCKOW](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/yockow/32/20636_2.png) [@YOCKOW](https://forums.swift.org/u/YOCKOW)\
**Post date:** [March 27, 2025, 2:46am UTC](https://forums.swift.org/t/potential-false-positive-sending-risks-causing-data-races/78859/2 "2025-03-27T02:46:56Z")

</div>

FWIW, my workaround is like this:

```swift
@MainActor
func main3() async {
    nonisolated(unsafe) let ns = NonSendable()
    await { @Sendable in await foo(ns) }()
    await runBlock { @Sendable in await foo(ns) }
}

```

- Declare `ns` with `nonisolated(unsafe)`
- Make every closure `@Sendable` explicitly.

I'm not sure if it's safe, though.

---

<div class="post-metadata">

**Author:** ![bobko](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/bobko/32/37854_2.png) [@bobko](https://forums.swift.org/u/bobko)\
**Post date:** [March 28, 2025, 9:26am UTC](https://forums.swift.org/t/potential-false-positive-sending-risks-causing-data-races/78859/3 "2025-03-28T09:26:49Z")

</div>

I found the workaround to just declare `runBlockMainActor`:

```swift
class NonSendable {}

@MainActor
func main2() async {
    let ns = NonSendable()
    await runBlockMainActor { await foo(ns) }
    await runBlockMainActor { await foo(ns) }
}

func foo(_ a: Any) async {}

@MainActor
func runBlockMainActor(_ block: @MainActor () async -> ()) async {
    await block()
}

```

I tried to declare `runBlockMainActor` as generic, but unfortunately it doesn't work: [False positive `sending '...' risks causing data races`. Global isolation vs parameter isolation. Actor as generic · Issue #80368 · swiftlang/swift · GitHub](https://github.com/swiftlang/swift/issues/80368)

---

<div class="post-metadata">

**Author:** ![CrystDragon](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/crystdragon/32/10461_2.png) [@CrystDragon](https://forums.swift.org/u/CrystDragon)\
**Post date:** [March 28, 2025, 10:39am UTC](https://forums.swift.org/t/potential-false-positive-sending-risks-causing-data-races/78859/4 "2025-03-28T10:39:26Z")

</div>

I think the compiler erroneously deduced the `{ await foo(ns) }` closure to be a `@MainActor` one, thus transfering `ns` into the `MainActor` domain.

I've found 2 different ways to bypass this problem, without leveraging unsafe features. Both work by keeping `ns` out of the `MainActor` region.

1. use a local function, which is explicitly marked `nonisolated`

```swift
@MainActor
func main2() async {
    let ns = NonSendable()
    nonisolated func block() async {
        await foo(ns)
    }

    await block()
}

```

1. mark the closure parameter in `runBlock` as `sending`:

```swift
@MainActor
func main2() async {
    let ns = NonSendable()
    await runBlock { await foo(ns) }
}

func runBlock(isolation: isolated (any Actor)? = #isolation, _ block: sending () async -> ()) async {
    await block()
}

```

---

<div class="post-metadata">

**Author:** ![bobko](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/bobko/32/37854_2.png) [@bobko](https://forums.swift.org/u/bobko)\
**Post date:** [March 28, 2025, 10:54am UTC](https://forums.swift.org/t/potential-false-positive-sending-risks-causing-data-races/78859/5 "2025-03-28T10:54:27Z")

</div>

Unfortunately, the suggested solutions don't work

1. is no different from `main1` in the original post
2. doesn't work when you use `ns` multiple times:

```swift
@MainActor
func main2() async {
    let ns = NonSendable()
    await runBlock { await foo(ns) } // error: value of non-Sendable type '@noescape @async @callee_guaranteed () -> ()' accessed after being transferred; later accesses could race
    await runBlock { await foo(ns) }
}

```

---

<div class="post-metadata">

**Author:** ![CrystDragon](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/crystdragon/32/10461_2.png) [@CrystDragon](https://forums.swift.org/u/CrystDragon)\
**Post date:** [March 28, 2025, 11:36am UTC](https://forums.swift.org/t/potential-false-positive-sending-risks-causing-data-races/78859/6 "2025-03-28T11:36:31Z")

</div>

Yes, method 2 has its own requirement. But why do you say method 1 is no different?

You can write

```swift
@MainActor
func main2() async {
    let ns = NonSendable()
    nonisolated func block() async {
        await foo(ns)
    }

    await runBlock(block)
    await runBlock(block)
}

func foo(_ a: Any) async {}

func runBlock(isolation: isolated (any Actor)? = #isolation, _ block: () async -> ()) async {
    await block()
}

```

---

<div class="post-metadata">

**Author:** ![bobko](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/bobko/32/37854_2.png) [@bobko](https://forums.swift.org/u/bobko)\
**Post date:** [March 28, 2025, 1:08pm UTC](https://forums.swift.org/t/potential-false-positive-sending-risks-causing-data-races/78859/7 "2025-03-28T13:08:14Z")

</div>

Good point, thanks, now I agree.

Actually, now when `nonisolated` is explicitly spelled out, the whole situation confuses me even more. So Swift doesn't complain when it's `nonisloated`, but it **does** complain when `func block()` is isolated to `@MainActor`.

Isn't the isolation to `@MainActor` better than being `nonisolated`? In case of isolation to `@MainActor`, `ns` doesn't cross any isolation domains, it always stays in the `MainActor` isolation domain -- there are no races.

I am confused...

---

<div class="post-metadata">

**Author:** ![Andropov](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/andropov/32/35955_2.png) [@Andropov](https://forums.swift.org/u/Andropov)\
**Post date:** [March 29, 2025, 4:49pm UTC](https://forums.swift.org/t/potential-false-positive-sending-risks-causing-data-races/78859/8 "2025-03-29T16:49:44Z")

</div>

> [@bobko](#):
>
> ```swift
> func runBlock(
> isolation: isolated (any Actor)? = #isolation,
> _ block: () async -> ()
> ) async {
> await block()
> }
> 
> ```

Uh, this is tricky, but I think I know what's going on here.

By using `isolation: isolated (any Actor)? = #isolation`, you're telling the compiler that `runBlock` runs in the same isolation domain as the caller. However (and this is the tricky bit), the compiler doesn't know that `block` should also run on the same isolation domain as the caller.

This is more or less the exact issue described as one of the motivation examples for [Run nonisolated async functions in the caller actor by default (SE-0461)](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0461-async-function-isolation.md#motivation). Another clue that this is the underlying issue is that annotating `block` with the (underscored, not stable) attribute `@_inheritActorContext` works:

```swift
func runBlock(
    isolation: isolated (any Actor)? = #isolation,
    @_inheritActorContext _ block: () async -> ()
) async {
    await block()
}

```

So the compiler sees that `runBlock` has no specific isolation requirements for its `block` parameter, therefore it must do sendability checks for the closure. And then it detects that the closure you're passing to `block` includes a non-sendable value.

The only way to pass that non-sendable value across isolation domains is by `sending` it. But although `sending` works in a function, for example in your `main1` function:

```swift
@MainActor
func main1() async {
    let ns = NonSendable()
    await foo(ns)
    await foo(ns)
}

```

It doesn't work for the closure. Generally speaking, `sending` a value to a function as an argument is more reliable than creating a closure where that same value is sent to the function and passing _that_ closure as an argument to a function.

That's because the function that receives the closure is allowed (depending on a combination of other factors, like whether the closure is `@escaping`/`async`/`@Sendable`) to do weirder things with the closure, like calling it more than once or calling it multiple times in parallel which may mess up the logic of whether some value can be sent.
