[Pitch #2] On Actors and Initialization

Hi folks,

I've got an all-new proposal to fix how actor initialization works, based on feedback from the first review. There are also changes to actor initializer delegation, stored property isolation, and deinitializers.

Since the proposal differs significantly from the first review, this is a pitch. Here's a very informal and incomplete summary of the major functionality proposed, along with some links into the document itself for more details:

  1. Actor initializers that are not isolated to the actor will now allow you to do anything you normally can from a nonisolated method. In exchange, Swift will automatically reject accesses to stored properties that might be unsafe. Here is the problem description and proposed solution.
  2. Deinitializers of an actor can no longer access non-sendable stored properties of the instance. Here is the problem description and proposed solution
  3. A type's stored property cannot have a default value if its isolation is not compatible with its initializers. Here is the problem description and proposed solution
  4. The convenience keyword is no longer required to define a delegating initializer of an actor. Here is the problem description and proposed rules for their delegating initializers, which is continued in the Sendability section.

The proposal is lengthy, but I've tried to always follow each discussion with an example, and have isolated the more esoteric discussions under "In-depth Discussion" headings at the end of some sections. As always, I greatly appreciate any time you spend helping to improve Swift by reading this proposal!

17 Likes

IIUC, this is the "control-flow-based" checking we discussed in the first review. I like that it aligns (synchronous) initializers with nonisolated methods, so it's easier to reason about. And if we eventually get non-escaping parameters, one will be able to do a lot more in initializers for free.

Yeah, this makes sense.

I'm not sure the complexity of this solution is worth it. The solution asks us to consider all of the initializers:

For a given type with some stored property, a default value can only be provided if the isolation of the property is compatible with the isolation of all non-async and non-delegating initializers of that type. An isolated property is compatible if it is not isolated, or all such initializers have the same isolation.

... but the initializers might be scattered among different source files, and we have to look at the initializer bodies to determine whether they are delegating or not (now that convenience is not required), which implies a lot of work for the compiler. How bad would it be if we said that the default values of stored properties in actors have to be evaluated as if in a nonisolated context? And the same for default values of stored properties in global-actor-constrained structs/classes/enums?

Yippee!

Doug

1 Like

Yes I think this would work just fine. We should say that the nonisolated context applies to all stored properties of a type that can be isolated to an actor. Having their default-value expressions be an actor-isolated context causes more trouble than it's probably worth. My attempt to preserve the power of syntactic sugar would make the language more complex. Programmers can always opt to do the initialization in an init with the appropriate actor-isolation if nonisolated doesn't work for them.

For global or static stored properties, it's not syntactic sugar, so I think its initializing expression needs to remain actor isolated to support something like this:

@MainActor
var x = 20

@MainActor 
var y = x + 2

Agreed!

Yes, I agree.

Doug