Why can a sendable closure call a non-sendable closure?

Why does this top-level code compile (Swift version 6.1.2, language mode 6)?

enum Something {

    @MainActor static var value = 0

}

let closure: () -> Void = {
    Something.value += 1
}

let sendableClosure: @Sendable () -> Void = {
    closure()
}

Is it a compiler bug/missing feature or is it the intended behavior?

3 Likes

The issue you found seems to be specific to MainActor. If I change it to a custom global actor, the code doesn't compile (as expected).

enum Something {
    @MyActor static var value = 0
}

let closure: () -> Void = {
    Something.value += 1
}

let sendableClosure: @Sendable () -> Void = {
    Something.value += 1
    closure()
}

I suspect what happened in your code is that:

  • Since closure captures a variable in MainActor, compiler infers that its signature is @MainActor () -> Void (though it's odd that this doesn't work for custom global actor. See above code).
  • But later when compiler compiles sendableClosure(). It somehow uses () -> Void, instead of the inferred one @MainActor () -> Void for closure().

I'd suggest you to file a bug to avoid the issue gets silently ignored.

Thank you for checking. I'll file a bug.

The issue: Inconsistent isolation of top-level closure · Issue #81599 · swiftlang/swift · GitHub

1 Like