Is dispatchPrecondition a reasonable way to implement an @unchecked Sendable type?

If the type is safe to send around to multiple threads because its instances are only ever used from a specific globally-singleton thread, that is precisely the intended use case for global actors, in this case (because the thread is the main thread) @MainActor. You ought to be able to make the initializer nonisolated if you need to construct an object outside the main actor. If you have any trouble with that, that's worth a bug.

@unsafe Sendable is an acceptable workaround if you really can't find any way to express the safety constraints you're working with, but you're going to have a much better experience overall if you can put the effort in to get the formal isolation right.

In general, the right thing to do depends on why your class is safe:

  • If your class instances can be safely referenced by multiple threads[1] because their stored properties are all immutable lets, your class can just be Sendable.

  • If your class instances can be safely referenced by multiple threads because in practice their stored properties are all immutable, but for some reason (e.g. a complex initialization pattern that completes before the object is shared across threads) some of the properties have to be declared as mutable vars, that is a perfectly reasonable use of @unchecked Sendable. Consider adding some sort of lifecycle assertion to your setters, e.g. a "this is immutable now" flag.

  • If your class instances can be safely referenced by multiple threads because their mutable storage is only actually accessed from a globally-singleton thread, your class should be associated with a global actor.

  • If your class instances can be safely referenced by multiple threads because their mutable storage is only actually accessed under a lock, that is a reasonable use of @unchecked Sendable. This is an important pattern that we're working on safe ways to express.

  • If your class instances can't generally be safely referenced by multiple threads, and in fact they aren't referenced by multiple threads and just get created, used, and destroyed on a single thread, they should not have to be Sendable at all. In this case, it's worth figuring out why you think you need to do so. It's possible that you're actually doing something dangerous, or maybe you've got some funny sort of concurrent context that Swift doesn't understand behaves like an isolated serial executor. Consider if there's an alternative way to express your pattern in a way that Swift will understand.

  • If your class instances can't generally be safely referenced by multiple threads, but instances do need to be moved between threads occasionally, you should not use @unchecked Sendable on the class. Instead, you should suppress the warning locally by "smuggling" the object across the sendability boundary: make a value of an @unchecked Sendable struct that holds the object, then send that instead. This is safe as long as you really do stop using the object in the original context (and only transfer it to one context at a time), and it's much better than pretending that any send is safe. This is a very important pattern that we're actively working on safe ways to express.

  • If your class instances can be safely referenced by multiple threads because their mutable storage is only actually accessed from one of the threads at a time, but that thread isn't globally singleton, the accesses aren't mediated by something like a lock, and you really do maintain active references on multiple threads... I mean, I'm willing to assume that you've got some justification for why this is being done safely, but this seems like a very treacherous pattern, and it's hard to imagine Swift ever finding a way to support it safely. Consider whether you can find a more structured way to express this. If not, you're going to have to just use @unchecked Sendable on the class and accept that you're losing out on concurrency safety.


  1. By "thread" I really mean any concurrent context: a thread, a queue, an actor, whatever. But it's a lot more succinct to just say "thread". ↩︎

14 Likes