Łukasz
Łukasz

Reputation: 805

Is it better way to avoid „Cannot use mutating member on immutable” in var

I have a struct with var grid

struct Machina {
    ....
    var grid: [Whatever] = []
    var other: Something
    ... // other stuff
}

But grid needs to be counted every time other changes. So, I can call

var other: Something { 
    didSet {
        defineGrid()
    }
...

mutating func defineGrid() {
    var result: [Whatever] 
    ... // count result
    grid = result
}

but I would love to simply call grid as var and keep counted grid in _grid

var _grid: [Whatever]? = nil
var other: Something { 
    didSet {
        _grid = nil
    }
...

mutating func defineGrid() {
    var result: [Whatever] 
    ... // count result
    _grid = result
}
...
var grid: [Whatever] {
    if _grid == nil {
        defineGrid() // error: Cannot use mutating member on immutable value: 'self' is immutable
    }
    return _grid!
}

but in this case I got an error. Is a first approach nice? Is it possible to get var grid from second approach? Maybe it's just a cosmetics. I don't know.

Upvotes: 1

Views: 89

Answers (2)

user28434'mstep
user28434'mstep

Reputation: 6600

If Swift 5 is used you can also use this handy @propertyWrapper:

@propertyWrapper
struct LazyWithReset<T> {
    private var value: T? = nil // this is hidden storage for the value, like _grid in the question

    var wrappedValue: T { // this is lazy-ish interface to access/generate value, like grid in the question
        mutating get {
            if (value == nil) {
                value = generator()
            }
            return value!
        }
    }
    var generator: () -> T // this is the function that sets value, like defineGrid in the question

    init(_ generator: @escaping () -> T) {
        self.generator = generator
    }

    mutating func reset() { // this is just resetting the storage while keeping internals hidden
        value = nil
    }
}

Usage:

struct Machina {
    @LazyWithReset(defineGrid)
    private(set) var grid: [Whatever] // no initial value required, generator will lazily provide value if needed

    var other: Something {
        didSet {
            _grid.reset() // variable set, we reset our lazy variable
        }
    }

    private static func defineGrid() -> [Whatever] { // now it's pure static function, no mutability required
        return … // whatever you was directly setting to the grid var previously
    }
}

This way you can hide all boilerplate code under the rug and keep your business logic clean.

Also LazyWithReset<T> can be reused wherever you want, so you won't have to copy-paste any code.

Upvotes: 1

Sweeper
Sweeper

Reputation: 271905

As Alexander said, you need a mutating getter for grid:

var grid: [Whatever] {
    mutating get {
        if _grid == nil {
            defineGrid()
        }
        return _grid!
    }
}

Also, _grid should probably be private.

Upvotes: 1

Related Questions