# Method of MainActor-isolated value not called on the main actor

**URL:** <https://forums.swift.org/t/method-of-mainactor-isolated-value-not-called-on-the-main-actor/78526>\
**Category:** Using Swift\
**Tags:** concurrency\
**Created:** [March 14, 2025, 11:55am UTC](https://forums.swift.org/t/method-of-mainactor-isolated-value-not-called-on-the-main-actor/78526 "2025-03-14T11:55:36Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![gwendal.roue](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/gwendal.roue/32/13992_2.png) [@gwendal.roue](https://forums.swift.org/u/gwendal.roue)\
**Post date:** [March 14, 2025, 11:55am UTC](https://forums.swift.org/t/method-of-mainactor-isolated-value-not-called-on-the-main-actor/78526/1 "2025-03-14T11:55:36Z")

</div>

Hello,

I just fixed a bug in my app, where a `CurrentValueSubject` would not publish a value on the main actor, as I expected.

The relevant code was:

```swift
// subject is MainActor-isolated
@MainActor let subject = CurrentValueSubject(...)

// In a non-isolated function,
// I await the subject before I send a value in it,
// expecting the value to be published on
// the main actor:
await subject.send(...)

```

Well, `send` is not called on the main actor, as I could witness (see below for a reproducing case).

The fix was to explicitly call `send` from the MainActor:

```diff
-await subject.send(...)
+await MainActor.run {
+ subject.send(...)
+}

```

I can't tell if this is a compiler bug (Xcode 16.2, 16C5032a), or if this is expected (but surprising and borderline hostile).

If anyone is interested, this executable reproduces the problem:

```swift
/// This class takes place of CurrentValueSubject.
/// It is not sendable, and in our particular case we
/// want to make sure its `send` method is called on the
/// main actor.
class MySubject {
    func send() {
        MainActor.assumeIsolated {
            print("Good: on main thread")
        }
    }
}

/// A class that has a MainActor-isolated subject, and calls its `send`
/// method from a non-isolated context.
final class Context: Sendable {
    @MainActor let subject = MySubject()
    
    init() { }
    
    func send() async {
        await subject.send()
    }
}

@main struct App {
    static func main() async {
        let context = Context()
        // Will it crash, or print "Good: on main thread"?
        // Well, it crashes.
        await context.send()
    }
}

```

---

<div class="post-metadata">

**Author:** ![gwendal.roue](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/gwendal.roue/32/13992_2.png) [@gwendal.roue](https://forums.swift.org/u/gwendal.roue)\
**Post date:** [March 14, 2025, 12:12pm UTC](https://forums.swift.org/t/method-of-mainactor-isolated-value-not-called-on-the-main-actor/78526/2 "2025-03-14T12:12:58Z")

</div>

> I can't tell if this is a compiler bug (Xcode 16.2, 16C5032a), or if this is expected (but surprising and borderline hostile).

It's a bug. The compiler, in Swift 6 language mode, should not allow the non-Sendable `subject` to cross isolation domains:

```swift
// @MainActor let subject = MySubject()

await subject.send()
// <-----> Main actor
// <----> Somewhere else, as proven by the reproducing example

```

The only correct options are:

1. Either call `send()` on the main actor.
2. Either refuse to compile and emit a diagnostic.

To go further: if Region-Based-Isolation would allow, in some circumstances, to accept such code, then _consistency_ would strongly suggest that `send()` is _always_ called on the main actor, in order to avoid surprises and subtle/unexpected behavior changes.

---

<div class="post-metadata">

**Author:** ![gwendal.roue](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/gwendal.roue/32/13992_2.png) [@gwendal.roue](https://forums.swift.org/u/gwendal.roue)\
**Post date:** [March 14, 2025, 12:27pm UTC](https://forums.swift.org/t/method-of-mainactor-isolated-value-not-called-on-the-main-actor/78526/3 "2025-03-14T12:27:53Z")

</div>

> [@gwendal.roue](#):
>
> To go further: if Region-Based-Isolation would allow, in some circumstances, to accept such code, then _consistency_ would strongly suggest that `send()` is _always_ called on the main actor, in order to avoid surprises and subtle/unexpected behavior changes.

There we are: a crasher that involves `sending`. No compiler warning, no compiler error, but a runtime crash:

```swift
/// This class takes place of CurrentValueSubject.
/// It is not sendable, and in our particular case we
/// want to make sure its `send` method is called on the
/// main actor.
class MySubject {
    func send() {
        MainActor.assumeIsolated {
            print("Good: on main thread")
        }
    }
}

@available(*, unavailable)
extension MySubject: Sendable { }

/// A class that builds a MainActor-isolated subject, and calls its `send`
/// method from a non-isolated context.
final class Context: Sendable {
    @MainActor func makeSubject() -> sending MySubject {
        MySubject()
    }
    
    func send() async {
        await makeSubject().send()
    }
}

@main struct App {
    static func main() async {
        let context = Context()
        // Will it crash? Yes it crashes.
        await context.send()
    }
}

```

---

<div class="post-metadata">

**Author:** ![ph1ps](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/ph1ps/32/35459_2.png) [@ph1ps](https://forums.swift.org/u/ph1ps)\
**Post date:** [March 14, 2025, 12:35pm UTC](https://forums.swift.org/t/method-of-mainactor-isolated-value-not-called-on-the-main-actor/78526/4 "2025-03-14T12:35:32Z")

</div>

> [@gwendal.roue](#):
>
> There we are: a crasher that involves `sending`. No compiler warning, no compiler error, but a runtime crash:

The crash is to be expected, this is from the docs of `assumeIsolated`:

> If the current context is not running on the actor’s serial executor, or if the actor is a reference to a remote actor, this method will crash with a fatal error (similar to `preconditionIsolated()`).

`MySubject.send` is a synchronous non-isolated function, meaning it will inherit the isolation of the caller. `Context.send` is an asynchronous non-isolated function, meaning it will run on the global executor.

`makeSubject`'s isolation is not really relevant in this case. Just the creation of `MySubject` will be `MainActor`-isolated. After that it will switch back to the global executor and run `MySubject.send` on there.

You'd need to annotate `Context.send` with `@MainActor`. You will then also be able to drop the `await` in front of `mySubject().send()`.

---

<div class="post-metadata">

**Author:** ![gwendal.roue](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/gwendal.roue/32/13992_2.png) [@gwendal.roue](https://forums.swift.org/u/gwendal.roue)\
**Post date:** [March 14, 2025, 12:39pm UTC](https://forums.swift.org/t/method-of-mainactor-isolated-value-not-called-on-the-main-actor/78526/5 "2025-03-14T12:39:05Z")

</div>

That's fine. And to be honest, my initial reproducing code does not create any warning in the Swift 5 language mode, but does in the Swift 6 language mode.

The [second crasher](https://forums.swift.org/t/method-of-mainactor-isolated-value-not-called-on-the-main-actor/78526/3) compiles fine in the Swift 6 language mode, and is an actual bug.

---

<div class="post-metadata">

**Author:** ![ph1ps](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/ph1ps/32/35459_2.png) [@ph1ps](https://forums.swift.org/u/ph1ps)\
**Post date:** [March 14, 2025, 12:40pm UTC](https://forums.swift.org/t/method-of-mainactor-isolated-value-not-called-on-the-main-actor/78526/6 "2025-03-14T12:40:23Z")

</div>

I am talking about the second snippet. I don't think there is a bug. `MainActor.assumeIsolated` is supposed to crash if you are not on `MainActor`.

---

<div class="post-metadata">

**Author:** ![gwendal.roue](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/gwendal.roue/32/13992_2.png) [@gwendal.roue](https://forums.swift.org/u/gwendal.roue)\
**Post date:** [March 14, 2025, 12:41pm UTC](https://forums.swift.org/t/method-of-mainactor-isolated-value-not-called-on-the-main-actor/78526/7 "2025-03-14T12:41:53Z")

</div>

Oh, so the crossing of isolation domains is allowed because of Region-Based isolation, then.

All right, to be filed in "correct behavior that is waiting to bite you" (shrug).

---

<div class="post-metadata">

**Author:** ![gwendal.roue](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/gwendal.roue/32/13992_2.png) [@gwendal.roue](https://forums.swift.org/u/gwendal.roue)\
**Post date:** [March 14, 2025, 12:56pm UTC](https://forums.swift.org/t/method-of-mainactor-isolated-value-not-called-on-the-main-actor/78526/8 "2025-03-14T12:56:15Z")

</div>

To sum up: thank you @ph1ps for your correct analysis. I was developing in the Swift 5 language mode, and I was bitten. I still think that the [second sample code](https://forums.swift.org/t/method-of-mainactor-isolated-value-not-called-on-the-main-actor/78526/3) can surprise a lot of people, and is borderline hostile.

---

<div class="post-metadata">

**Author:** ![jamieQ](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/jamieq/32/5117_2.png) [@jamieQ](https://forums.swift.org/u/jamieQ)\
**Post date:** [March 14, 2025, 1:12pm UTC](https://forums.swift.org/t/method-of-mainactor-isolated-value-not-called-on-the-main-actor/78526/9 "2025-03-14T13:12:33Z")

</div>

any thoughts on ways this could be surfaced as potential unexpected behavior? perhaps the key issue in this case is that it's ambiguous at the callsite as to what exactly is being awaited. e.g.

```swift
// is it this?
(await makeSubject()).send() // ✅

// or this?
await (await makeSubject()).send() // ⚠️: no 'async' operations occur within 'await' expression

```

not sure if there is a pattern here that would be generically useful to detect, but maybe there is the seed of some form of lint rule?
