Actor cannot conform to Codable?

I'm experimenting with actors under Swift 5.5. I have an actor with a single attribute, a dictionary [String: Int] . I want to make my actor conform to Codable , but since my attribute is actor-isolated, and since encode(to encoder: Encoder) is nonisolated, conforming to Codable doesn't seem possible as it requires accessing actor-isolated mutable state.

Serializing actors would seem to be a useful thing, so how does the community recommend we go about this?

final actor MyActor: Encodable {
    private var values: [String: Int]

    init(values: [String: Int]) {
        self.values = values
    }
    
    nonisolated func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        let v = values // ⛔️ Actor-isolated property 'values' can not be referenced from a non-isolated context
        try container.encode(v)
    }
}

I think this is currently not possible. As you say, you can either have an isolated method that doesn’t witness the Codable protocol, or a non-isolated method that can’t access the actor’s state.

SE-0313 Actor Isolation Control has as a future direction ‘isolated conformances’, which look like they would do what you need here. From within an actor isolated method/function, you would be able to encode that actor’s state.

here's my attempt at this problem.

caveat: I'm not an expert on actors, so may have misunderstood. Feedback welcome.

the approach is to store my data in a class which can be codable (and where the properties are not isolated), but to only otherwise access those properties from an actor which is held by the class.

I think this gives full actor isolation as required, with the exception that the codable methods completely ignore isolation.

class StatReporter: Codable {

    private var reports : [Report]
    var actor:StatActor!
    
    enum CodingKeys: String, CodingKey {
        case reports
    }
    
    init(reports:[Report] = []) {
        self.reports = reports
        self.actor = StatActor(parent:self)
    }
    
    actor StatActor {
        var parent:StatReporter
        
        internal init(parent: StatReporter) {
            self.parent = parent
        }
        
        var reports:[Report] {
            get {
                parent.reports
            }
            set {
                parent.reports = newValue
            }
        }
  
     //add your methods here
 }
}

so, I can now call aStatReporter.actor.doSomething()

where doSomething() will be isolated

reports is private, so nothing outside the actor (or other methods I add in the class) can access it.

This doesn't work. The StatReporter class has mutable state (reports) that is actor-protected everywhere except the encode function of Codable. Anything that changes reports via the actor will sequentialized by the actor, but the call to encode will not.

That means it isn't safe to encode the class, due to the possibility of simultaneous write accesses to the state from different tasks.

It's inescapable that, if you want to safely encode mutable state belonging to an actor, you need an isolated method to do it.

did you read my post?

I think this gives full actor isolation as required, with the exception that the codable methods completely ignore isolation.

Having said that, if you call any encoding from a single function within the actor (which is itself protected), then I think you'll be protected.

Is it more useful to have an actor that is codable (with the codable not isolated) - or to just ditch using actors altogether if you want codable? That's your call.

Yes, but this has two consequences:

  1. The function needs to be awaited. That means it cannot be called directly from a synchronous encode function outside your class.

  2. Your outer class doesn't have an usable synchronous function the satisfies the Codable conformance it promises (and, worse, has an unusable synchronous function that is dangerous if called).

In other words, your class isn't actually Codable. If you choose to provide an actor-isolated encoding function of some kind, then a better solution would be to invert the hierarchy, and make the outer class an actor, and the inner class a private Codable-conforming type (preferably a struct).

But that wouldn't make your class Codable.