Why actor/globalActor initialisers isolation (Swift 6.0)

Hey all!

I don't understand why actors init (also for global actors) need to be isolated. When I try to mark the initializer as nonisolated, I get the following error:

Global actor 'MainActor'-isolated property 'container' cannot be mutated from a non-isolated context; this is an error in Swift 6.

Why do initializers have to be isolated at all?
Can I really initialize the same object from different threads and got an concurrency-error?
It seems impossible to do so.

Could someone explain how this works and why initializers must be isolated? :pray:

Here's one example to consider:

@MainActor struct Foo {
  var x = SomeUIKitClass.crashesWhenCalledOffMainThread()

  nonisolated init() {
    // There is no possible way for this to run
  }
}

Keeping in mind that default values are implicitly assigned in every init, there is no possible way for a nonisolated init to work here. If the initializer runs off the main thread, the default value cannot be (synchronously) retrieved. So, there are really two choices:

  • @MainActor types can have fields whose default values are themselves @MainActor;
  • @MainActor types can have nonisolated inits.

Not having the former would be more unintuitive (at least in my opinion), so Swift forbids the latter instead.

1 Like

Yes, you right. Sorry, but I'm referring to the simpler case.

actor SomeActor {
  let dependency1: String
  
  nonisolated init(dependency1: String) { // 'nonisolated' on an actor's synchronous initializer is invalid; this is an error in Swift 6
    self.dependency1 = dependency1
  }
}

@MainActor
final class SomeMainActor {
  let dependency1: DependencyProtocol
  
  nonisolated init(dependency1: DependencyProtocol) { // Main actor-isolated property 'dependency1' can not be mutated from a non-isolated context; this is an error in Swift 6
    self.dependency1 = dependency1
  }
}

In this case, I just want to initialize the actor's property. I don't intend to access any properties after initialization.
I simply want to set the actor's properties during initialization, so I don't see any concurrency issues in this scenario. :worried:

Non-global actors already have synchronous, non-isolated initializers. So you can drop the nonisolated from the init and it will work just fine:

actor SomeActor {
  let dependency1: String
  init(dependency1: String) {
    self.dependency1 = dependency1
  }
}
func synchronousContext() {
  let someActor = SomeActor(dependency1: "Hello")  // No 'await' needed
}

And then global actors do have isolated initializers, but also they must for the reasons that @bbrk24 outlined above.

3 Likes

@paca-waca you have comments referring to property container but I don't see that in the snippet?

sorry, it's a mistake, fixed

thx, I got it about non-global actors