Cannot use mutating getter on immutable value: 'self' is immutable error

Because when you call x inside the struct, it's not clear whether the contentView itself is mutable or not. A value type gets mutable only when it is defined as a var.

So it would help if you wrapped it with an immutable value before using it inside a builder function inside the struct.

like this:

func xValue() -> Double {
    var mutatableSelf = self
    return mutatableSelf.x
}

var body: some View {
    VStack {
        Text("\(xValue())")
    }
}

💡Note: Lazy property's value will be associated on the first call and this mutates the object. So they are considered as mutating


A getter cannot mutate

This is mainly a design Swift is enforcing with its getters. The principle is:

The getters should not mutate the object. Because developers may not be expecting that. They should only expect a change when you're using the setter or calling a mutating function. A getter is neither of them.

The following example works as expected:

struct Device {
    var isOn = true
}

let x = Device()
let y = Device()

y.isOn // Doing such will not cause the object to mutate.

Yet the following example, the getter will have a side-effect. The Swift architecture just doesn't allow it.

struct Device2 {
    
    var x = 3
    var isOn: Bool {
        x = 5
        return true
    }
}

let a = Device2()
let b = Device2()

a.isOn // Doing such will mutate the object. a.x will be '5'. While `b.x` will be '3'. Swift doesn't want to allow this.

Lazy is mutating:

struct ContentView: View {
    lazy var x : Double = 3.0

    var body: some View {
       Text("\(x)") // ERROR
    }
}

Will result in the follow ERROR:

Cannot use mutating getter on immutable value: 'self' is immutable

SwiftUI - special case

Because the body variable is a computed property, you can't mutate/set variables. There's a way around that though.

Mark the variable with a @State property wrapper.

Example. The following code won't compile:

struct ContentView: View {
    var currentDate = Date()
    let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()

    var body: some View {
        Text("\(currentDate)")
            .onReceive(timer) { input in
                currentDate = input // ERROR: Cannot assign to property: 'self' is immutable
        }
    }
}

Yet the following will, just because it has @State

struct ContentView: View {
    @State var currentDate = Date()
    let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()

    var body: some View {
        Text("\(currentDate)")
            .onReceive(timer) { input in
                currentDate = input
        }
    }
}

For more on that see here