Thanks for the sharing. I think what you said is: if a change to variable A should be visible/shared by variable B, then variable A should be reference type. I completely agree with that. But I find there are other factors (e.g. language constraints) affecting the decision in the practice. Below is a real example in my code where I'd like to use struct but ended up using class, because otherwise I couldn't work around the "Escaping closure captures mutating self parameter" error.
I'm refactoring a small module in my app to use state machine. The module is a struct and have a set of methods. The new design is that each method call to this struct is translated to an event to the state machine in the struct.
My first thought was to implement the state machine as a struct too. Below is the pseudo code (for simplicity I don't use generic type):
struct StateMachine {
var currentState: State
var transitions: [Transitions]
// Add a transition to the state machine.
// onEntry param is a closure executed when transisting to the new state.
mutating func addTransition(event: Event, from: State, to: State, onEntry: (() -> Void)? = nil) {
...
}
// Send an event to the state machine
mutating func receive(event: Event) {
...
}
}
struct MyModule {
var sm: StateMachine
mutating func setupStateMachine() {
// Note this can't compile.
// Error: Escaping closure captures mutating self parameter
sm.addTransition(event: .eventA, from: .idle, to: .stateA) {
// Do something
}
}
mutating func methodA() {
sm.receive(event: .eventA)
}
}
Two observations:
-
All the above methods have to be mutating. For
StateMachine.addTransition(), it's because it modifiestransitionsarray. ForStateMachine.receive(), it's because it modifiescurrentState. ForMyModule.setupStateMachine(), it's because it callssm.addTransition()and hence modifiessm. ForMyModule.methodA(), it's because it callssm.receive()and hence modifiessm. -
The above code doesn't compile because the closure in
MyModule.setupStatemachine()causes the "Escaping closure captures mutating self parameter" error.
The error occurs because:
- The closure passed to
sm.addTransition()has to be escaping. - The
MyModule.smproperty has to be modified bysm.addTransition()(and hence bysetupStateMachine()).
I think this is quite a simple design and I doubt if there exists any reasonable design in which the above two points don't hold true. If so, that means the only possible way to resolve the compilation error is to change StateMachine to a class.
Side note: the more I deal with issue like this, the more I have the impression that, while closure really shines in functional programming, where variables are immutable, it seems to cause a lot of issues with mutable value types.