If I have a struct that has a mutating function and takes a closure, I know it is illegal to mutate the struct from within the closure. AKA:
struct User {
var name = "Roo"
mutating func setName(_ name: String, callback: () -> Void) {
self.name = name
callback()
}
}
var u = User()
u.setName("Kanga") { u.name = "haha" }
This will (correctly) generate a runtime exclusivity violation.
So far so good. If I make User a class instead of a struct and remove the mutating attribute, no error will happen at runtime.
My question is, is this just because it couldn't detect a violation or is it not considered a violation with a reference type because there is no well defined mutation duration.
The individual mutations to name in your example are disjoint and don't conflict with each other. The exclusivity conflict arises because the call to setName, as a mutating method on a value type, is considered to be a mutation of the entire variable u, which then conflicts with the mutation of u.name in the closure. There's no equivalent rule for the "entire class" with reference types: exclusivity is applied to all the stored properties individually. That means that if User were a class type, the only mutations would be those non-overlapping mutations to name, so there wouldn't be a conflict.
I'd expect the exclusivity violation in your example to be diagnosed statically, by the way.
For those who are interested, this is diagnosed at run time because the closure here names a global variable rather than capturing a local variable.
var u = User()
u.setName("Kanga") { u.name = "haha" }
It's an unfortunate distinction between top-level code and code within a function body. To diagnose this statically, the compiler would need to analyze all closure bodies even though they don't mutably capture anything. It's possible to do this, but I'd much rather just not have top-level variable declarations treated like globals--that will always be a source of confusion where the compiler behavior is significantly different.