Having programmed with identity types for years, I find mutating value types very stressful to use due to the constant risk of accidentally assigning (and thus copying) to a new variable and then mutating that copy and expecting to see those changes reflected in the original struct (example given at the end).
My actual question: What approaches/coding styles/practices are there to prevent this sort of thing from happening?
What I'm thinking: I can't imagine the answer to this question simply being "just remember that you're dealing with a struct and not a class", because that's extremely error prone especially since class-ness/struct-ness is entirely opaque to code using any particular data structure.
I also see a lot of talk around using "purely functional style", which I'm all for except:
- seems very awkward to do in cases where the mutating method also needs to return a value (as in my example below). Of course I could return a tuple like
(newStructInstance, returnValue)
but that can't be The Way. - I imagine that instantiating new copies of structs whenever you want mutations can't be in any way efficient (unlike reassignment of structs which, as I understand it, creates a new instance without incurring any of the costs).
Some possible answers to my question could be:
- your example is contrived and this sort of thing rarely, if ever, happens in practice
- you're using the iterator (anti)pattern (see code below), so that's your original sin right there
- ease up off of
mutating
and just use usestruct
s for immutable types andclass
es for mutable ones
Is any of this in the right direction?
Example: Say we have a struct that simply wraps an integer which it increments by 1 with a mutating method next()
.
struct Counter {
var i: Int = 0
mutating func next() -> {
self.i += 1
return self.i
}
}
In some initializer somewhere we instantiate like this
self.propertyWithLongAndAnnoyingName = Counter()
and later in the same scope, having forgotten that this property holds a struct, we assign it to the succinctly named local variable p
and increment that.
var p = self.propertyWithLongAndAnnoyingName
d.next() // actually increments a copy