Swift concurrency nOOb question: if you're using Approachable Concurrency and you get warnings about using Sendable structs in non-main-actor contexts, is the recommended workaround to mark them as nonisolated?

Also, it seems odd to me that marking a struct as Sendable doesn't already exempt it from default MainActor isolation. Is there a good reason why you'd want to limit Sendable types to MainActor?

@nicklockwood Imagine a video encoder, and a SwiftUI progress bar showing the encoding progress, so you have an `EncodingState` class to store percentage and maybe status text.

SwiftUI can only deal with MainActor, so you want EncodingState's fields to be MainActor-isolated.

But the encoder is running on a bg thread and periodically updating the state. So you want EncodingState to be Sendable to bg thread.

This ensures bg thread can update state via async, and SwiftUI gets main actor updates.

@nicklockwood the following are my thoughts wrt this question, I‘m not sure any qualify as „answer“.

MainActor-isolated should already imply Sendable, so I‘m not sure why you had to add Sendable conformance. Am I missing something?

My understanding of Approachable Concurrency is that the idea is that most people don‘t actually need as much concurrency as you get by default and „do everything on the main thread (except for explicit exceptions)“ makes your code much easier to understand without significant performance issues in the majority of cases. Based on that it makes sense to make all your structs MainActor-isolated so keep this simplicity independent of whether it‘s needed for Sendability.

@nicklockwood I just realized I misread the error message you talked about. I thought it was about using non-sendable structs in a non-main-actor context.

Why does using *Sendable* structs in a different context generate warnings at all? Like, why would this be an issue?

@cocoafrog that is the essence of my confusion, yes 😅

But it seems that if a struct is marked s MainActor isolated (either explicitly or implicitly via the compiler default MainActor isolation setting) then the fact that is also marked as Sendable is of no consequence.

@nicklockwood right. On the one hand marking it as Sendable being of no consequence makes sense, because it‘s already sendable due to MainActor-isolation. On the other hand that is also kinda confusing. 😄
@cocoafrog @nicklockwood I think Nick essentially wants that if the compiler is in mainactor-default mode, an explicit Sendable conformance also makes the type non isolated.
The problem seems specific to the default main actor mode, because w/o it you wouldn’t isolate a type *and* mark it Sendable (except for ABI, I think you have to do it there, not sure).
@nicklockwood Sendability and Isolation are orthogonal concepts. Your structs are still MainActor-isolated, and just because they are Sendable doesn't mean you can access them outside MainActor. In fact, they are automatically Sendable precisely because you can only access them serially on MainActor.
For global-actor-isolated types Sendable is guaranteed by accessing on a serial queue.
For nonisolated types, Sendable is guaranteed by immutability or by internal synchronisation.
@nicklockwood so answering your question more directly, yes if you want to directly access Sendable types outside MainActor you need to mark them as nonisolated. But then you'll lose Sendable-for-free for these types and need to think about a different way of conforming to it.

@alexozun sure, I understand that. But I'm talking about structs that are explicitly marked as Sendable and that the compiler has already approved as being conformant to Sendability requirements (regardless of the compiler-level concurrency defaults)

IUUC, conforming a type to Sendable means saying that it's safe to access from multiple actors concurrently, so why would you ever want a type that is marked as Sendable to also be MainActor-isolated?

@nicklockwood > "Sendable means saying that it's safe to access from multiple actors concurrently"
Nitpick: access from multiple isolation contexts, not actors. If your data is isolated to MainActor you can only access it on this actor. Sendable just allows you to safely pass this data between isolation boundaries, but in the end you're still forced to access it on MainActor.
Sendable+nonisolated: cross boundaries and access directly.
Sendable+MainActor: cross boundaries and access on MainActor.
@alexozun @nicklockwood this sums it up, great explanation 🙌
@nicklockwood Some people are saying to switch off the default MainActor isolation. @mattiem I think has some thoughts on this.
@matadan @nicklockwood @mattiem 100% this. It causes far, far more problems than it solves, IMO. The fact there’s a huge language dialect in the same version, with differences that affects all code you import (including libraries, not designed with MainActor-default-isolation) is a bit baffling to me. I’d be far more in favour of it as an idea if it didn’t affect libraries you import…
@rhysmorgan @matadan @nicklockwood what do you mean about it affecting libraries you import?
@mattiem @matadan @nicklockwood As in, when your Xcode project is MainActor-isolated, the modules you import are affected by this MainActor-isolation too (at least as far as I’m aware?). I know PointFree and GRDB have both been affected by this quite significantly. Loads of people asking questions on the PointFree Slack where the solution is usually “switch to nonisolated default isolation”
@mattiem @matadan @nicklockwood Sorry, yeah, I should have clarified – the issue is the MainActor isolation, not Approachable Concurrency!

@rhysmorgan @matadan @nicklockwood right so this mode has a profound type system impact, including exposing a bunch of previously/-unknown quirks.

Libraries are indirectly affected because it takes work (and is sometimes not possible) to hide the complexity from clients.

And because nonisolated is the “normal state”, that’s often just the easiest solution. You usually have to understand a lot to use MainActor default.

@mattiem @rhysmorgan @matadan @nicklockwood and some of the biggest sources of isolation compatibility issues are library macros, property wrappers, and other forms of codegen/synthesis. It feels like DAI affects imported libraries but technically it's all consumer code since it's emitted into the consumer module. So libs need to go an extra mile to ensure emitted code works for all consumers with various language dialects.

@mattiem @rhysmorgan @matadan @nicklockwood Language dialects profoundly affect the usage of libraries.

At https://github.com/groue/GRDB.swift/issues/1823#issuecomment-3362706069 you can see the differences. You see regular API usage, followed by the many changes required with MainActor by default.

- Explicit nonisolated keyword is required on type declarations that conform to a SendableMetatype protocol, unless the conformance is specified directly on the type declaration. Declaring the conformance in an extension, even in the same file, does not lift the burden of specifying nonisolated. Extensions are no longer a key language organization feature in the MainActor by default dialect. Great design.

- Explicit nonisolated keyword is required on conformances to protocols even when the type is already known to be nonisolated. If MyType is nonisolated, you still need to tell that its conformance to Equatable is not MainActor-isolated. Like, as if anyone would want something else.

- Explicit nonisolated keyword must be added on nonisolated methods required by nonisolated protocols. It won't compile until you do.

- No ability for the library protocols to declare that an associated type must be nonisolated.

Yeah that's a lot of explicit "nonisolated" that are required. And that's a lot of obscure compiler errors until you add all of them.

All of this *in the file of the type declaration*, i.e. where the developer is expressing the core intentions about the type.

Circular reference error when adding `MutablePersistableRecord` conformance. · Issue #1823 · groue/GRDB.swift

I'm new to GRDB so it's possible I did something wrong here, but I cannot add MutablePersistableRecord conformance to my Codable structs without getting a circular reference. I copy/pasted the Auth...

GitHub

@matadan @mattiem doing that does indeed silence the warnings.

I don't want to swim against the tide though - do we anticipate that default MainActor isolation will become the default at some point?

When I first heard about approachable concurrency, default MainActor seemed like the headline feature, but having now tried it in practice it doesn't seem very "approachable" at all.

@nicklockwood @mattiem Xcode is setting it as default but I think it is one of those things you will remember to uncheck each time. Can never be certain whether the next Xcode release will flip it back anyway.
@matadan @mattiem I think when I started this (new) project it wasn't enabled, but perhaps it was remembering my preference from a previous occasion?
@nicklockwood @mattiem I definitely have to remember to check this setting when I make a new project using Xcode 26.2.

@matadan @nicklockwood it’s enabled for new projects in 26. The plan is mode forever.

It’s very hard to use. There are a surprising number of unique and challenging problems it can present. It also exposes a large number of compiler quirks, but I expect the team will try to address those for June.

@mattiem @matadan @nicklockwood I think it’s actually enabled for any new _targets_, even in existing projects. Which makes it even worse.
@calicoding @matadan @nicklockwood yes, I think this is the case, but only app targets specifically, I'm pretty sure
@mattiem @matadan @nicklockwood I think I’ve seen it enabled for framework targets, not just app targets. At least for Approachable Concurrency

@nicklockwood since no one else seems to have said it directly.

MainActor default is an independent feature from the thing in Xcode labeled “Approachable Concurrency”.

And yes. When you turn on that default, it is normal to need to make things nonisolated.

@nicklockwood and for the second part, I was surprised explicit Sendable does not exempt things too. But yes, there is are good reasons and they are outlined in the proposal.

But in practice I think that is rarely what you actually want. Nonisolated types are “normal”, and you’ll find you have to use that keyword a lot in this mode.

@nicklockwood I wish we could have configured isolation per file. There was discussion about this during main actor by default proposal, would be interesting to ping again.
In any case, my logic is if it’s DTO or some other transferring model—yes, should be nonisolated to any domain. If it’s presentation model, then safe to have it isolated to main actor, and you don’t need to mark it sendable.