0

In Swift, let’s say we have an actor, which has a private struct.
We want this actor to be reactive, and to give access to a publisher that publishes a specific field of the private struct.

The following code seems to work. But it produces a warning I do not understand.

public actor MyActor {
    
    private struct MyStruct {
        var publicField: String
    }
    
    @Published
    private var myStruct: MyStruct?
    
    /* We force a non isolated so the property is still accessible from other contexts w/o await in other modules. */
    public nonisolated let publicFieldPublisher: AnyPublisher<String?, Never>
    
    init() {
        self.publicFieldPublisher = _myStruct.projectedValue.map{ $0?.publicField }.eraseToAnyPublisher()
    }
    
}

The warning:

Actor 'self' can only be passed 'inout' from an async initializer

What does that mean? Is it possible to get rid of this warning? Is it “dangerous” (can this cause issues later)?

Note:
If I use $myStruct instead of _myStruct.projectedValue, it does not compile at all. I think it’s related, but I don’t truly see how.

The error is in that case:

'self' used in property access '$myStruct' before all stored properties are initialized
Frizlab
  • 846
  • 9
  • 30

1 Answers1

0

The warning is because when you are using _myStruct.projectedValue compiler notes that you are trying to do mutation on the actor from a synchronous context. All you have to do to remove the warning is to make your initializer asynchronous using async specifier.

The actor initializer proposal goes into more detail for why initializer needs to be asynchronous if there mutations inside actor initializer. Cosider the following case:

actor Clicker {
  var count: Int
  func click() { self.count += 1 }

  init(bad: Void) {
    self.count = 0
    // no actor hop happens, because non-async init.

    Task { await self.click() }

    self.click() //  this mutation races with the task!

    print(self.count) //  Can print 1 or 2!
  }
}

Since there are two click method call one from Task and one called directly, there might be a case that the Task call executed first and hence the next call need to wait until that finishes.

Soumya Mahunt
  • 2,148
  • 12
  • 30