@MainActor
class UserSettings {
var isFeatureEnabled = false
}
It is really incongruous to me that, given the code above, I can get the value of isFeatureEnabled from a non-main-actor context, but I cannot set the value:
// fine:
let isFeatureEnabled = await settings.isFeatureEnabled
// very not fine:
await settings.isFeatureEnabled = true
This feels like unnecessary ceremony and is prone to typographical naming errors and leads to inconsistencies in that sometimes I can do .isFeatureEnabled = value and sometimes I must use the method.
This requires me to know that the settings type is @MainActor, even though the type is already decorated with it. It adds the complexity of additional scoping.
MainActor-isolated task:
Task { @MainActor in settings.isFeatureEnabled = true }
This has bothered me a lot as well. I'm not entirely sure, but it feels like this may be related to the reason why properties with accessor effects can't be mutable. My understanding is that there are multiple points of failure with no straightforward way to ubiquitously handle them. I could be wrong, of course.
I believe the primary reason it is not permitted to set properties asynchronously is that the new value is often dependent (directly or indirectly) on the old value.
Therefore, it would likely be semantically incorrect for the calculation of the new value and the setting of that value to not be in a single synchronous section, and thus Swift requires you to extract a method to do the combined calculate&set operation.
Swift does not do value-specific reasoning that could change compiling code to non-compiling code. This is to allow the programmer to extract subexpressions manually.
I have to admit I had to read this several times to understand what you were talking about. It sounds like you're saying that setting properties asynchronously would potentially involve multiple awaits, which means there could be races between getting a value and invoking the setter?
Something like:
await foo.bar = something()
... could easily read foo.bar through some degree of indirection, causing a race between getting .bar and setting .bar. Is that correct?
If that's what you're saying.... we already have this problem:
actor Foo {
var value: Int {
get async { 42 }
}
func setValue(adding ints: Int...) { value = ints.reduce(0, +) }
}
let foo = Foo()
await a.setValue(adding: foo.value, foo.value, foo.value, foo.value)
Every invocation of foo.value in the parameter list is an async operation, before (or after) which something else could cause foo to yield execution, which could cause the .value to change.
This is because await does not represent a single async operation, it represents a suspension point, during which any number of async operations could happen.
So... we already have race conditions around getting and setting properties. How would allowing await foo.value = 42 be any different from what we already have?