Search code examples
swiftswift-structs

How can I easily copy an immutable struct in swift, varying only some fields [Like kotlin dataclass copy method]?


I have a swift struct somewhat like this:

struct LogicalState {
  let a: String?
  let b: Bool
  let c: Int
}

and a mutable instance of this state. Note the properties within the state are all let, so the struct itself is immutable.

var _state: LogicalState

What I'd like to do is enforce a pattern where updating the state is allowed, but all updates must be atomic - I do NOT want to simply make a, b, and c mutable, as that would allow a and b to change independently. I need to control the updates and apply validation (for example, to enforce that if you change a, you also must change b at the same time)

I can do this by simply overwriting the whole struct

_state = LogicalState(a: "newA", b: false, c: _state.c)

However, as you can see, having to explicitly reference the old state for the properties that don't change (_state.c) is annoying and problematic, especially when you have more properties. My real-world example has something like 10.

In kotlin, they have "data classes" which expose a "copy" method, which lets you change only the parameters you want. If swift supported such a thing, the syntax would look like this

func copy(a: String? = self.a, b:Bool = self.b, c:Int = self.c) ...

The problem is, the = self.a syntax doesn't exist in swift, and I'm not sure of what other options I have?

Any solution on how to solve this would be much appreciated


Solution

  • Think, you can extend the struct with a copy(...) method taking nil values as default and replacing them with instance ones while using non-nil otherwise. E.g. something like this:

    extension LogicalState {
        func copy(a: String? = nil, b: Bool? = nil, c: Int? = nil) -> LogicalState {
            return LogicalState(a: a ?? self.a, b: b ?? self.b, c: c ?? self.c)
        }
    }
    

    So you can use it to copy instance while varying the needed params:

    let state = LogicalState(a: "A", b: false, c: 10)
    let stateCopy1 = state.copy(c: 30)
    let stateCopy2 = state.copy(a: "copy 2")