Strict isolation with subclasses

Hello folks,

I am working on a Swift binding to a third party system (Godot - GitHub - migueldeicaza/SwiftGodot: New Godot bindings for Swift), and I am trying to make my binding be strict-concurrent safe.

I expose an object-oriented wrapper in Swift that matches the underlying object model of Godot, this is basically a 1:1 mapping to what they have to offer.

They have an entire hierarchy of objects, starting with Object, and while some of the classes are thread-safe and can be called from any thread, others are not.

For example these chains of classes are thread safe:

Object > Input
Object > CameraServer
Object > RefCounted > TextServer > TextServerExtension > TextServerAdvanced

But other chains are not, for example:

Object > Node
Object > Node > Viewport > Window

So I do need things like Node, Viewport and Window to be annotated as @MainActor, but do not want Object, or RefCounted to be annotated with it.

But the compiler informs me that I am not able to do this:

public class Object {

public class Node: Object {
Main actor-isolated class 'Node' has different actor isolation from nonisolated superclass 'Object'; this is an error in the Swift 6 language mode

I am at a loss on how to proceed.


You might be interested in SE-0434: Usability of global-actor-isolated types. Specifically this section: swift-evolution/proposals/ at main · apple/swift-evolution · GitHub



Thank you so much Holly!

You folks are always five steps ahead.

Trying it out now.

1 Like

...well, I forgot to actually commit the patch that implements this section of the proposal. Sorry about that, and here it is! [Concurrency] Allow isolation on subclasses of non-Sendable, non-isolated superclasses. by hborla · Pull Request #73497 · apple/swift · GitHub


Thanks, I will try out the daily builds.

The 6.0 toolchain from a day or two ago is crashing for me for an issue unrelated to this, so I will keep my eye on the fresh builds, hoping this gets fixed.

In the meantime, I came up with an ugly hack, and I am wondering if this is a terrible idea, or if we think this is an acceptable workaround:

class Base { 
    // This allows me to construct this from any actors:
    nonisolated init () { }
    nonisolated func demo () { 
       // No MainActor affinity 

class Derived: Base {
    public override init () {  
      // Instances of derived run on MainActor
    func regular () {
       // This method only runs in MainActor

Task {
    let a = Base () // This works, no await necessary
    let b = await Derived () // Need to use await here due to @MainActor

This seemed to work without warnings, but I have a feeling that I might have stublmed into a loophole.


1 Like