Class only protocol - why the examples are not working?

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:

  1. All the above methods have to be mutating. For StateMachine.addTransition(), it's because it modifies transitions array. For StateMachine.receive(), it's because it modifies currentState. For MyModule.setupStateMachine(), it's because it calls sm.addTransition() and hence modifies sm. For MyModule.methodA(), it's because it calls sm.receive() and hence modifies sm.

  2. 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.sm property has to be modified by sm.addTransition() (and hence by setupStateMachine()).

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.