How to use Codable on a tree in Swift6

I'm having trouble getting my tree class to build with Swift6:

@MainActor
final class Node: Codable { // (X) Main actor-isolated property 'sub' can not be
                            //     referenced from a nonisolated context
                            // (X) Main actor-isolated property 'x' can not be
                            //     referenced from a nonisolated context
    var sub: Node
    var x: String
    // ... many other fields
}

We can make all the fields final but now we need a beefy constructor or builder for non-Codable construction, which is extra code:

@MainActor
final class Node: Codable {
    let sub: Node
    let x: String
    // ... many other fields
}

If we use a struct then we risk unintentionally copying the large tree of data. I'm not sure how to know when Swift copies data. Swift's syntax doesn't make it clear at all. Also, we need to use our own custom Box<T> class because, sadly, Swift doesn't include one. Even searching for Box is hard because, sadly, docs.swift.org doesn't have a search box.

@MainActor
struct Node2: Codable {
    var sub: Box<Node2>
    var x: String
    // ... many other fields
}

Is there any chance Codable will get an upgrade to work with Swift6?
Is there a better workaround to make Codable work with Swift6?

Maybe combine the two approaches? A class that holds a struct, this way you could use a member-wise initialiser feature of a struct and the reference semantics of a class.

Full example
@MainActor final class Node: Codable {
    struct Storage: Codable {
        let x: String
        let sub: Node?
    }
    var storage: Storage
    
    enum CodingKeys: String, CodingKey {
        case sub
        case x
    }
    init(storage: Storage) {
        self.storage = storage
    }
    nonisolated required init(from decoder: any Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let x = try container.decode(String.self, forKey: .x)
        let sub = try container.decodeIfPresent(Node.self, forKey: .sub)
        storage = Storage(x: x, sub: sub)
    }
    nonisolated func encode(to encoder: any Encoder) throws {
        let (x, sub) = MainActor.assumeIsolated {
            (storage.x, storage.sub)
        }
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(x, forKey: .x)
        try container.encodeIfPresent(sub, forKey: .sub)
    }
}

Re the Box - I'd love to see your version..

Typically you'd use an Array, like so:

struct Node {
    var x: String
    var subNodes: [Node]
}

which array you could manually restrict to only have 0 or 1 items for a singular linked tree (list).

Or use an indirect enum:

indirect enum Node {
    case leaf(x: String)
    case nonLeaf(x: String, sub: Node)
}

which could be also written as:

enum Node {
    case leaf(x: String)
    indirect case nonLeaf(x: String, sub: Node)
}
1 Like

Can you use an isolated conformance to Codable?

@MainActor
final class Node: @MainActor Codable { … }

Edit: I’m not sure on the status of this feature. Maybe it’s available with a flag in Xcode 26?