# Types that must be MainActor, but cannot be MainActor due to protocol conformance

**URL:** <https://forums.swift.org/t/types-that-must-be-mainactor-but-cannot-be-mainactor-due-to-protocol-conformance/70507>\
**Category:** Using Swift\
**Tags:** concurrency\
**Created:** [March 7, 2024, 10:58pm UTC](https://forums.swift.org/t/types-that-must-be-mainactor-but-cannot-be-mainactor-due-to-protocol-conformance/70507 "2024-03-07T22:58:26Z")\
**Posts on this page:** 11\
**Page:** 1

<div class="post-metadata">

**Author:** ![cocoaphony](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/cocoaphony/32/3872_2.png) [@cocoaphony](https://forums.swift.org/u/cocoaphony)\
**Post date:** [March 7, 2024, 10:58pm UTC](https://forums.swift.org/t/types-that-must-be-mainactor-but-cannot-be-mainactor-due-to-protocol-conformance/70507/1 "2024-03-07T22:58:26Z")

</div>

In the following simplified code, we're getting stuck with MainActor being both forbidden and required. We have a ViewModifier that observes a ObservableObject. Therefore it must be MainActor (and in fact, becomes MainActor automatically due to the ObservedObject wrapper).

However, it is used within a ButtonStyle. ButtonStyle requires a non-isololated `makeBody` method. But if it's nonisolated, then it can't access the ViewModifier. Under "complete" concurrency, the following either generates "Call to main actor-isolated initializer 'init()' in a synchronous nonisolated context" or ""Main actor-isolated instance method 'makeBody(configuration:)' cannot be used to satisfy nonisolated protocol requirement."

My belief is that this is a SwiftUI bug. If `View.body` is MainActor, `ViewModifier.body(content:)` should be as well. But until SwiftUI changes, or in the case that it never changes, is there any way out of this corner?

```swift
// A global "configuration" class that things can observe to turn on and off the debugOverlay
// It is MainActor to ensure @Published only emits on the main actors, and to permit the create of `.global`
@MainActor
final class Config: ObservableObject {
    static let global: Config = .init()
    @Published public var debugOverlayEnabled = false
}

// An overlay that displays whenever the global debugOverlayEnabled changes
// Using @ObservedObject quietly makes the MainActor
private struct DebugComponentOverlay: ViewModifier {
    @ObservedObject var globalConfiguration = Config.global

    func body(content: Content) -> some View {
        content.overlay {
            if globalConfiguration.debugOverlayEnabled {
                Color.green.opacity(0.25)
            }
        }
    }
}

// Now we're stuck. This needs to be MainActor in order to use DebugComponentOverlay,
// but cannot be MainActor and still conform to `ButtonStyle`
// "Main actor-isolated instance method 'makeBody(configuration:)' cannot be used to satisfy nonisolated protocol requirement
struct LinkButtonStyle: ButtonStyle {
    func makeBody(configuration: Configuration) -> some View {
        Button("Text") {}
            .modifier(DebugComponentOverlay())
    }
}

```

---

<div class="post-metadata">

**Author:** ![Matt\_Gallagher](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/matt_gallagher/32/2685_2.png) [@Matt\_Gallagher](https://forums.swift.org/u/Matt_Gallagher)\
**Post date:** [March 7, 2024, 11:15pm UTC](https://forums.swift.org/t/types-that-must-be-mainactor-but-cannot-be-mainactor-due-to-protocol-conformance/70507/2 "2024-03-07T23:15:10Z")

</div>

It looks like Swift will let you get away with the following:

```swift
@MainActor
struct LinkButtonStyle: ButtonStyle {
    private let overlay = DebugComponentOverlay()
    nonisolated func makeBody(configuration: Configuration) -> some View {
        Button("Text") {}
            .modifier(overlay)
    }
}

```

although that doesn't seem totally valid to me (since I feel like a `nonisolated` shouldn't be allowed to access a `MainActor` instance variable).

---

<div class="post-metadata">

**Author:** ![cocoaphony](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/cocoaphony/32/3872_2.png) [@cocoaphony](https://forums.swift.org/u/cocoaphony)\
**Post date:** [March 7, 2024, 11:15pm UTC](https://forums.swift.org/t/types-that-must-be-mainactor-but-cannot-be-mainactor-due-to-protocol-conformance/70507/3 "2024-03-07T23:15:29Z")

</div>

@Helge_Hess1 on Mastodon just answered this for me, and it works as expected.

> **[Helge Heß (@helge@mastodon.social)](https://mastodon.social/@helge/112056929106066934)**
>
> @cocoaphony @holly@hachyderm.io Would this thing by @mattiem help in any way? I.e. \`MainActor.assumeIsolated\` https://github.com/mattmassicotte/ConcurrencyRecipes/blob/main/Recipes/Protocols.md#solution-1-non-isolated-conformance

```swift
struct LinkButtonStyle: ButtonStyle {
    func makeBody(configuration: Configuration) -> some View {
        MainActor.assumeIsolated { // <------
            Button("Text") {}
                .modifier(DebugComponentOverlay())
        }
    }
}

```

---

<div class="post-metadata">

**Author:** ![cocoaphony](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/cocoaphony/32/3872_2.png) [@cocoaphony](https://forums.swift.org/u/cocoaphony)\
**Post date:** [March 7, 2024, 11:16pm UTC](https://forums.swift.org/t/types-that-must-be-mainactor-but-cannot-be-mainactor-due-to-protocol-conformance/70507/4 "2024-03-07T23:16:22Z")

</div>

Have you checked this with "concurrency=complete?" I don't think it works.

---

<div class="post-metadata">

**Author:** ![Matt\_Gallagher](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/matt_gallagher/32/2685_2.png) [@Matt\_Gallagher](https://forums.swift.org/u/Matt_Gallagher)\
**Post date:** [March 7, 2024, 11:17pm UTC](https://forums.swift.org/t/types-that-must-be-mainactor-but-cannot-be-mainactor-due-to-protocol-conformance/70507/5 "2024-03-07T23:17:22Z")

</div>

Yes, this is with `-strict-concurrency\=complete`. But I prefer the `assumeIsolated` anyway.

---

<div class="post-metadata">

**Author:** ![James\_Dempsey](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/james_dempsey/32/14336_2.png) [@James\_Dempsey](https://forums.swift.org/u/James_Dempsey)\
**Post date:** [March 7, 2024, 11:46pm UTC](https://forums.swift.org/t/types-that-must-be-mainactor-but-cannot-be-mainactor-due-to-protocol-conformance/70507/6 "2024-03-07T23:46:34Z")

</div>

> [@cocoaphony](#):
>
> Have you checked this with "concurrency=complete?" I don't think it works.

> [@Matt\_Gallagher](#):
>
> Yes, this is with `-strict-concurrency\=complete`. But I prefer the `assumeIsolated` anyway.

Note that `-strict-concurrency=complete` will warn about things in Swift 5.10 that it didn't warn about in earlier versions. So your experience may vary depending on which version of Swift you are using.

---

<div class="post-metadata">

**Author:** ![hborla](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/hborla/32/27563_2.png) [@hborla](https://forums.swift.org/u/hborla)\
**Post date:** [March 8, 2024, 12:05am UTC](https://forums.swift.org/t/types-that-must-be-mainactor-but-cannot-be-mainactor-due-to-protocol-conformance/70507/7 "2024-03-08T00:05:34Z")

</div>

Yeah, the recommended way to do this is using `MainActor.assumeIsolated`. The goal of [SE-0423: Dynamic actor isolation enforcement from non-strict-concurrency contexts](https://forums.swift.org/t/se-0423-dynamic-actor-isolation-enforcement-from-non-strict-concurrency-contexts/70155) is to streamline this pattern using `@preconcurrency` protocol conformances since this pattern is so pervasive and necessary to facilitate an incremental migration for an entire ecosystem of Swift code.

---

<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 9, 2024, 5:54am UTC](https://forums.swift.org/t/types-that-must-be-mainactor-but-cannot-be-mainactor-due-to-protocol-conformance/70507/8 "2024-03-09T05:54:05Z")

</div>

Thanks Holly for this explicit advice. You're one of a kind giving such explicit and practical advice and this is _greatly appreciated_. Really, I mean it, you're precious and unique so far.

I hope we won't have to use those magic incantations for too long. I mean - some people will totally understand it, and some other people will just blindly copy and paste until the compiler stops complaining. I can't decide if this is the signs of a language that has forgotten its progressive disclosure precept, or the sign of Apple SDKs that sadly lag behind the language (for unfortunate and probably very stupid reasons because everybody is 😳🙄🤔🫣😖😬⁉). I hope the problem is in the SDK and that everybody will synchronize _very soon_ because we're not in a pleasing situation. After all there are _plenty of teams_ at Apple that can beta-test your releases. I wonder why _we_ have to bite the dust.

---

<div class="post-metadata">

**Author:** ![cocoaphony](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/cocoaphony/32/3872_2.png) [@cocoaphony](https://forums.swift.org/u/cocoaphony)\
**Post date:** [March 9, 2024, 5:19pm UTC](https://forums.swift.org/t/types-that-must-be-mainactor-but-cannot-be-mainactor-due-to-protocol-conformance/70507/9 "2024-03-09T17:19:06Z")

</div>

I agree that "add `nonisolated` and then wrap in `assumeIsolated`" is a very ugly incantation. What speaks to me about SE-0423 and `@preconcurrency` is that it replaces that kind of "chant" with a clearly spelled explanation of what's happening, which is also very easy to remove once it's no longer necessary. I do expect people to add it too much, but the compiler even gives you a warning when you don't need it, helping avoid it becoming a vestigial cut-and-paste chant. (I don't know whose idea that was, but they clearly care about us very much and learned from our previous experiences. 🤗)

I do expect to see a similar pattern to implicitly unwrapped optionals. People used `!` types a lot, in bizarre ways that were completely unnecessary. (I still don't get why people thought marking every property in a simple struct with `!` was of any value, but I saw it constantly.) But, as the underlying libraries got audited, IUOs have steadily faded from our code, and I don't see our new developers copying them any more.

It took years, yes. And I think we should expect at least as long for `@preconcurrency` to fade. But that's ok. It was fine, and it'll be fine again. Choices like SE-0423 are a big deal IMO because they explicitly put a box around the issue, give it a name, and make it easy to remove like we removed all the `!` marks before. We should continue to be on the lookout for other situations where people have to write weird code just to work around a current problem, and apply similar solutions.

I'm sure I'm not the first to say it, but I think Swift 5.10 is a Swift 3 moment. Before Swift 3, Swift promised a lot, but struggled to really deliver. It was seriously debatable whether it was worth the trouble. With Swift 3, IMO, Swift became a real language. There were still lots of issues that took years to smooth out, but its promise was finally real. Swift Concurrency up to this point has been similar. Lots of promise, but in practice I've spent much more time fighting with it than getting real value out of it. I think 5.10, as a preview of Swift 6, changes that.

The fact that converting to `concurrency=complete` has, in just a few hours, exposed real threading bugs in our code, as well as finding a [long-existing mistake in UIKit](https://forums.swift.org/t/preconcurrency-notification-names-in-submodules/70514), tells me we are on the right track now.

I just need a built-in pattern for serial transactions and a semaphore, please. 😄

---

<div class="post-metadata">

**Author:** ![mickeyl](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/mickeyl/32/12258_2.png) [@mickeyl](https://forums.swift.org/u/mickeyl)\
**Post date:** [March 10, 2024, 10:05am UTC](https://forums.swift.org/t/types-that-must-be-mainactor-but-cannot-be-mainactor-due-to-protocol-conformance/70507/10 "2024-03-10T10:05:41Z")

</div>

Sorry, I don’t have something productive to add, but…

> [@gwendal.roue](#):
>
> I can't decide if this is the signs of a language that has forgotten its progressive disclosure precept, or the sign of Apple SDKs that sadly lag behind the language (for unfortunate and probably very stupid reasons because everybody is 😳🙄🤔🫣😖😬⁉). I hope the problem is in the SDK and that everybody will synchronize _very soon_ because we're not in a pleasing situation. After all there are _plenty of teams_ at Apple that can beta-test your releases. I wonder why _we_ have to bite the dust.

This, this, and a thousand times more: THIS! There are so many places now that fall apart if you peek behind the curtains. It’s sad to see such a high potential language being treated like that.

---

<div class="post-metadata">

**Author:** ![vanvoorden](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/vanvoorden/32/33619_2.png) [@vanvoorden](https://forums.swift.org/u/vanvoorden)\
**Post date:** [April 10, 2024, 12:06am UTC](https://forums.swift.org/t/types-that-must-be-mainactor-but-cannot-be-mainactor-due-to-protocol-conformance/70507/11 "2024-04-10T00:06:20Z")

</div>

[https://developer.apple.com/forums/thread/749566](https://developer.apple.com/forums/thread/749566)

I encountered a similar situation building a custom `SwiftUI.DynamicProperty` from a 6.0 toolchain. I'm using the `SE-0423` workaround for now. I guess I have to wait for the next version of SwiftUI to find out if this workaround is no longer needed after the WWDC release.
