I like value semantics in Swift but I am worried about the performance of mutating functions. Suppose we have the following struct
:
struct Point {
var x = 0.0
mutating func add(_ t:Double){
x += t
}
}
Now suppose we create a Point
and mutate it as so:
var p = Point()
p.add(1)
Now does the existing struct
in memory get mutated, or is self
replaced with a new instance as in:
self = Point(x:self.x+1)
Now does the existing struct in memory get mutated, or is self replaced with a new instance
Conceptually, these two options are exactly the same. I'll use this example struct, which uses UInt8 instead of Double (because its bits are easier to visualize).
struct Point {
var x: UInt8
var y: UInt8
mutating func add(x: UInt8){
self.x += x
}
}
and suppose I create a new instance of this struct:
var p = Point(x: 1, y: 2)
This statically allocates some memory on the stack. It'll look something like this:
00000000 00000001 00000010 00000000
<------^ ^------^ ^------^ ^----->
other |self.x | self.y | other memory
^----------------^
the p struct
Let's see what will happen in both situations when we call p.add(x: 3)
:
The existing struct is mutated in-place:
Our struct in memory will look like this:
00000000 00000100 00000010 00000000
<------^ ^------^ ^------^ ^----->
other | self.x | self.y | other memory
^----------------^
the p struct
Self is replaced with a new instance:
Our struct in memory will look like this:
00000000 00000100 00000010 00000000
<------^ ^------^ ^------^ ^----->
other | self.x | self.y | other memory
^----------------^
the p struct
Notice that there's no difference between the two scenarios. That's because assigning a new value to self causes in-place mutation. p
is always the same two bytes of memory on the stack. Assigning self a new value to p
will only replace the contents of those 2 bytes, but it'll still be the same two bytes.
Now there can be one difference between the two scenarios, and that deals with any possible side effects of the initializer. Suppose this is our struct, instead:
struct Point {
var x: UInt8
var y: UInt8
init(x: UInt8, y: UInt8) {
self.x = x
self.y = y
print("Init was run!")
}
mutating func add(x: UInt8){
self.x += x
}
}
When you run var p = Point(x: 1, y: 2)
, you'll see that Init was run!
is printed (as expected). But when you run p.add(x: 3)
, you'll see that nothing further is printed. This tells us that the initializer is not anew.