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)
}