Clearly the right direction and fixing a pretty insidious gap in concurrency safety.
Confusing example?
@MainActor func requiresMainActor() -> Int { ... }
class C {
@MainActor var x1 = requiresMainActor()
nonisolated init() async {
self.x1 = await requiresMainActor()
}
}
Forgive me if I'm missing something here, or if it's tangential, but isn't the default value for x1
unusable? Will this SEP introduce suitable compiler warnings (or errors) for unreachable code like this?
(that example aside, I like the rules laid out in Stored property initial values, they seem intuitive and flexible)
Argument evaluation order
For a given call, argument evaluation happens in the following order:
- Left-to-right evalution of explicit r-value arguments
- Left-to-right evaluation of default arguments
- Left-to-right evaluation of formal access arguments
Is that stating that this is what the order already is, or that the proposal will make it this way? And I assume it's talking about the case where (post this proposal at least) none of the default arguments require isolation?
Unlike the explicit argument list, isolated default arguments must be evaluated in the isolation domain of the callee. As such, if any of the argument values require the isolation of the callee, argument evaluation happens in the following order:
- Left-to-right evalution of explicit r-value arguments
- Left-to-right evaluation of formal access arguments
- Hop to the callee's isolation domain
- Left-to-right evaluation of default arguments
Is there a particular reason to evaluate all default arguments in the callee's isolation domain, as opposed to just the ones that are explicitly isolated to it?
From a practical perspective there's a difference in performance, potentially - e.g. if default argument evaluation is non-trivial then moving it to the callee might be beneficial for parallelism, even for arguments that don't need that isolation. At least, in cases such as actor initialisation… it could be harmful to performance, conversely, if the callee's isolation domain is a global actor (especially the main actor).
And so on the other hand, it creates a slightly more complicated and ill-defined ruleset as to what order arguments are evaluated in, arguably. Some might find it simpler if only callee-isolated default arguments are evaluated "out of [normal] order", leaving non-isolated default arguments unaffected. That makes it more predictable, at least, as to where the 'burden' of nonisolated default argument evaluation will land, as it depends solely on the call site rather than the callee's declaration (which can change out from under callers without any notice).
At least, if I'm understanding the proposal correctly.
Autosynthed inits with 'conflicting' isolation requirements
It is an error for two different default values to require different actor isolation, because it's not possible to ever use those default values. Initializing an instance of a type using two different initial value expressions with different actor isolation must be done in an async
initializer, with suspension points explicitly marked with await
.
Should the compiler synthesise suitable async initialisers in this case?
Multi-isolation-domain default arguments
Just to be crystal clear, what's the expected behaviour / usability of something like:
func foo(x: Int = requiresMainActor(),
y: Int = requiresOtherGlobalActor())
Will that be usable from anywhere (albeit with the available argument defaults varying based on caller isolation context), or is it not permitted at all?