Is there any "real" difference between `@MainActor @Sendable () -> ()` and `@MainActor () -> ()`

Per the documentation of Sendable, classes isolated to the Main Actor are implicitly Sendable. Closures are basically just a shorthand for a class with one function and only private state.

There's also this pitch which says it will "Infer the @Sendable attribute for global-actor-isolated functions and closures."

My intuition (which comes mainly from threads/GCD) says that if the compiler guarantees that a particular piece of code is always run on the main thread (or the equivalent in "isolation domains" or "global actors") then it doesn't matter how many threads have a reference to the function, because they always await it running on main.

So, should I always mark closures that are isolated to a global actor as @Sendable, in preparation for the day that the Swift compiler infers this for me? Or is there ever a situation where a globally isolated closure isn't sendable, or some quirk of the compiler that makes this a bad idea today?

1 Like

Hi,

Under the current concurrency rules, a non-Sendable globally-isolated closure is not really useful, as you can only use it if the current context is already isolated to the global actor (naturally, this won't require sendablity across isolation domains). Since the context is already isolated to the main actor, and the closure is non-Sendable, you don't even need to mark the closure main-actor isolated because it will be called from it anyway.

However, until SE-0434 lands, I think that from the usability perspective, you should mark globally-isolated closures as Sendable because then you'll be able to use them in, for example, a Task{} closure.

Your intuition is mostly correct, if the closure is isolated to the main actor, this means that it will never be called concurrently since all calls to it will be queued on the main actor (this is a very very abstract high-level intuition). So therefore, it is safe to infer @Sendable for usability of such a closure.

I hope this helps!

-- Sima

3 Likes

You could mark a closure @MainActor in an enum's associated value. This will enable you to pass around the closure to non-main actor, isolated contexts, and result in the compiler ensuring that when you're ready to call it, it will be on the main actor.