One thing I wonder is how much we can avoid the need to dynamically replace the main and global executors at all. The places where the main actor is implicit are fairly rare IIRC, so in most cases code can explicitly use a different global actor instead of replacing the one MainActor. And as for the default global executor, there was just another pitch to make the default actor configurable; although the scope of that pitch is currently only a binary choice between the default global executor and the MainActor, it seems like a reasonable extension to allow the default executor for a module or file to be set to any executor. With those features, how often would code need to dynamically take control of the one shared main and/or default global executor, rather than change the defaults for itself at build time?
For the case where a global actor's implementation needs to be chosen based on dynamic conditions, I wonder if we could generalize the design of global actors to allow for a global actor implementation to be existential. That would allow you to write something like:
@globalActor
public struct IOActor {
static var shared: any Actor = if ioUringSupported {
IOURingActor()
} else {
LibUVActor()
}
}
That would allow for this sort of dynamic executor implementation choice to be used for global actors more generally, not only the two special executors, and it would also provide some safety by construction, since the actor initialization would occur as part of the normal initialization on first use of the global, rather than needing to occur as part of the main program in the somewhat nebuluous "before any jobs are scheduled" process state.