How to copy a struct and modify one of its properties at the same time?

The answers here are ridiculous, especially in case struct's members change.

Let's understand how Swift works.

When a struct is set from one variable to another, the struct is automatically cloned in the new variable, i.e. the same structs are not related to each other.

struct A {
    let x: Int
    var y: Int
}

let a = A(x: 5, y: 10)
var a1 = a
a1.y = 69
print("a.y = \(a.y); a1.y = \(a1.y)") // prints "a.y = 10; a1.y = 69"

Keep in mind though that members in struct must be marked as var, not let, if you plan to change them.

More info here: https://docs.swift.org/swift-book/LanguageGuide/ClassesAndStructures.html

That's good, but if you still want to copy and modify in one line, add this function to your struct:

func changing<T>(path: WritableKeyPath<A, T>, to value: T) -> A {
    var clone = self
    clone[keyPath: path] = value
    return clone
}

Now the example from before changes to this:

let a = A(x: 5, y: 10)
let a1 = a.changing(path: \.y, to: 69)
print("a.y = \(a.y); a1.y = \(a1.y)") // prints "a.y = 10; a1.y = 69"

I see that adding 'changing' to a lot of struct would be painful, but an extension would be great:

protocol Changeable {}

extension Changeable {
    func changing<T>(path: WritableKeyPath<Self, T>, to value: T) -> Self {
        var clone = self
        clone[keyPath: path] = value
        return clone
    }
}

Extend your struct with 'Changeable' and you will have your 'changing' function.

With the 'changing' function approach, too, any property that you specify in the 'changing' function's call sites (i.e. of type WritableKeyPath) should be marked in the original struct as var, not let.


I really like @Shadow answer, but I had a hard time adapting it to a scenario were the fields of the struct could be nullable, so I decided to use the builder pattern instead. My code looks something like this:

struct A {
    let a: Int?
    let b: Int?

    class Builder {
        private var a: Int?
        private var b: Int?

        init(struct: A) {
            self.a = struct.a
            self.b = struct.b
        }

        func build() -> A {
            return A(a: self.a, b: self.b)
        }

        func withA(_ a: Int?) -> Builder {
            self.a = a
            return self
        }

        func withB(_ b: Int?) -> Builder {
            self.b = b
            return self
        }
    }
}

And then you can use it like:

A.Builder(struct: myA).withA(a).withB(nil).build()

With this my structs are really immutable, and I can quickly create a copy and change one or many of its field to be either nil or another Int


Note, that while you use placeholder values for constants a and b you are not able to construct instance of A with any other values but this placeholders. Write initializer instead. You may write custom method that change any value in struct also:

struct A {
    let a: Int
    let b: Int

    init(a: Int = 2, b: Int = 3) {
        self.a = a
        self.b = b
    }

    func changeValues(a: Int? = nil, b: Int? = nil) -> A {
        return A(a: a ?? self.a, b: b ?? self.b)
    }
}

let state = A()
let state2 = state.changeValues(b: 4)

If you can live with the properties being mutable, this is an alternative approach. Advantage is that it works for every struct and there's no need to change the function upon adding a property:

struct A {
    var a: Int
    var b: Int

    func changing(change: (inout A) -> Void) -> A {
        var a = self
        change(&a)
        return a
    }
}

let state = A(a: 2, b: 3)

let nextState = state.changing{ $0.b = 4 }

You could also have an extension for this:

protocol Changeable {}
extension Changeable {
    func changing(change: (inout Self) -> Void) -> Self {
        var a = self
        change(&a)
        return a
    }
}

extension A : Changeable {}

Also you can use this to do it without any additional code:

let nextState = {
    var a = state
    a.b = 4
    return a
}()

And if you don't mind the new struct being mutable, it's just

var nextState = state
nextState.b = 4