Does a Mutating Struct Function in Swift Create a New Copy of Self

Does a mutating struct function in swift create a new copy of self?

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):

  1. 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
  2. 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.

Accidentally mutating a copy of a struct instead of the struct itself

Your question, if it isn't just an elaborate troll, seems more psychological than practical, and as such is probably not a good fit for Stack Overflow: it's a matter of opinion. However, taking you seriously and assuming that the question is sincere, I can tell you as a matter of experience that, as someone who programmed for years in Objective-C and then learned Swift when it was first released to the public, the existence of value type objects such as a structs comes as a huge relief, not as a problem as you seem to posit.

In Swift, a struct is the object type par excellence. When you simply need an object, you use a struct. Using classes is quite exceptional, confined to cases such as the following:

  • You're using Cocoa (it's written in Objective-C and you just have to accept that its objects are classes)

  • You need a superclass / subclass relationship (very rare in pure Swift programming; usually this happens only because you are using Cocoa)

  • The object is to represent external reality where individual identity is crucial (for example, a UIView would need to be a reference type, not a value type, because there really are individual views sitting in the interface and we need to be able to distinguish them)

  • True mutability in place is a desideratum

In general, therefore, most programmers come to feel just the opposite of the state of mind you hypothesize: they are surprised when an object unexpectedly turns out to be a reference type (a class) and mutation affects a remote reference. In the code example that you give:

var p = self.propertyWithLongAndAnnoyingName
p.next()

...it would be a complete shock if self.propertyWithLongAndAnnoyingName were also mutated. Value types represent a form of safety. The assignment to p and mutation of p would be precisely to insulate self.propertyWithLongAndAnnoyingName from being changed.

In fact, Cocoa itself goes to great lengths to guard against this sort of thing. That is why, for example, the pattern is that propertyWithLongAndAnnoyingName would never be (as seen from the outside) an NSMutableString — it would be an NSString, which puts up a fence of immutability around the object even though it is a reference type. Thus the use of structs in Swifts solves a problem which Cocoa has to solve through much more elaborate and (to many beginners) arcane measures.

However, cases like this are relatively rare in actual fact, because another aspect of your hypothesis is also false:

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

My own experience is that in any situation where it matters, I always know whether I'm dealing with a struct or a class. Simply, if it's a Swift library object (String, Array, Int, etc.), it's a struct. (The library defines basically no classes at all.) If it's a Cocoa object (UIView, UIViewController), it's a class.

Indeed, the naming convention usually helps you. The Foundation overlay is a case in point. NSData is a class; Data is a struct.

Note too that you cannot mutate a let reference to a struct, but you can mutate a let reference to class. Thus in practice you very quickly experience which is which, because you always use let if you can.

Finally, if it is really important to you hand an object off to some third party for mutation-in-place, that is what inout is for. And in that case, you know it, because you have to pass a reference (address) explicitly. If the parameter you are passing to is not inout, you will assume that your original object is safe from mutation, and you'll be glad of it.

Swift and mutating struct

The mutability attribute is marked on a storage (constant or variable), not a type. You can think struct has two modes: mutable and immutable. If you assign a struct value to an immutable storage (we call it let or constant in Swift) the value becomes immutable mode, and you cannot change any state in the value. (including calling any mutating method)

If the value is assigned to a mutable storage (we call it var or variable in Swift), you're free to modify the state of them, and calling of mutating method is allowed.

In addition, classes don't have this immutable/mutable mode. IMO, this is because classes are usually used to represent reference-able entity. And reference-able entity is usually mutable because it's very hard to make and manage reference graphs of entities in immutable manner with proper performance. They may add this feature later, but not now at least.

For Objective-C programmers, mutable/immutable concepts are very familiar. In Objective-C we had two separated classes for each concept, but in Swift, you can do this with one struct. Half work.

For C/C++ programmers, this is also very familiar concept. This is exactly what const keyword do in C/C++.

Also, immutable value can be very nicely optimised. In theory, Swift compiler (or LLVM) can perform copy-elision on values passed by let, just like in C++. If you use immutable struct wisely, it will outperform refcounted classes.

Update

As @Joseph claimed this doesn't provide why, I am adding a little more.

Structs have two kind of methods. plain and mutating methods. Plain method implies immutable (or non-mutating). This separation exists only to support immutable semantics. An object in immutable mode shouldn't change its state at all.

Then, immutable methods must guarantee this semantic immutability. Which means it shouldn't change any internal value. So compiler disallows any state changes of itself in a immutable method. In contrast, mutating methods are free to modify states.

And then, you may have a question of why immutable is the default? That's because it's very hard to predict the future state of mutating values, and that usually becomes the main source of headaches and bugs. Many people agreed that the solution is avoiding mutable stuffs, and then immutable by default was on top of wish list for decades in C/C++ family languages and its derivations.

See purely functional style for more details. Anyway, we still need mutable stuffs because immutable stuffs have some weaknesses, and discussing about them seems to be out of topic.

I hope this helps.

Save struct in background mutating function

I think it will help if we minimally elicit the error message you're getting. (For delay, see dispatch_after - GCD in swift?.)

struct S {
var name = ""
mutating func test() {
delay(1) {
self.name = "Matt" // Error: Closure cannot ...
// ... implicitly capture a mutating self parameter
}
}
}

The reason lies in the peculiar nature of struct (and enum) mutation: namely, it doesn't really exist. When you set a property of a struct, what you're really doing is copying the struct instance and replacing it with another. That is why only a var-referenced struct instance can be mutated: the reference must be replaceable in order for the instance to be mutable.

Now we can see what's wrong with our code. Obviously it is legal for a mutating method to mutate self; that is what mutating means. But in this case we are offering to go away for a while and then suddenly reappear on the scene (after 1 second, in this case) and now mutate self. So we are going to maintain a copy of self until some disconnected moment in the future, when self will suddenly be somehow replaced. That is incoherent, not least because who knows how the original self may have been mutated in the meantime, rendering our copy imperfect; and the compiler prevents it.

The same issue does not arise with a nonescaping closure:

func f(_ f:()->()) {}

struct S {
var name = ""
mutating func test() {
f {
self.name = "Matt" // fine
}
}
}

That's because the closure is nonescaping; it is executed now, so the incoherency about what will happen in the future is absent. This is an important difference between escaping and nonescaping closures, and is one of the reasons why they are differentiated.

Also, the same issue does not arise with a class:

class C {
var name = ""
func test() {
delay(1) {
self.name = "Matt" // fine
}
}
}

That's because the class instance is captured by reference in the closure, and a class instance is mutable in place.

(See also my little essay here: https://stackoverflow.com/a/27366050/341994.)

What does it mean if a mutating function setting it's self equals to another function

I'm assuming that this fragment is in a struct rather than a class.

self.normalized() makes a copy of self and divides the copy's components by its length and then returns the copy. self is not affected.

self.normalize() gets a normalised version of self and then replaces self by the copy. So it changes in place.

Under the hood, every member function passes self as an implicit argument. i.e. to the compiler the declaration looks like this:

func normalised(self: SCNVector3) -> SCNVector3

Putting mutating on the front of a function definition makes the hidden argument inout

func normalise(self: inout SCNVector3)

So, if you have

var a = SCNVector3(3, 4, 0)
let b = SCNVector3(4, 3, 0)
let c = b.normalized()
a.normalize()

After that code, c would be (0.8, 0.6, 0) and a would be (0.6, 0.8, 0). b would be unchanged.

Note that a has to be declared with var because it is changed in place by normalise()

Edit

In the comments khan asks:

What i am not able to understand is why do we have to create a func again we can use "func normalized"

The point being made is why can't we do something like this:

    var a = SCNVector3(3, 4, 0)
a = a.normalized()

and not have the normalise() function at all?

The above will have exactly the same effect as a.normalize() and, in my opinion[1], is better style, being more "functional".

I think a.normalize() exists only because it is common in Swift to provide both forms of the function. For example, with sets you have both union() and formUnion(): the first returns the union of one set with another, the second replaces a set with the union of itself and another set. In some cases, the in place version of the function may be more efficient, but not, I think, with this normalize function.

Which you choose to use is a matter of your preference.


[1] Actually, the better style is

let a = SCNVector3(3, 4, 0).normalized()

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.

The mutated value get not updated in a function of struct that use that value in Swift/SwiftUI

When you call DispatchQueue.main.asyncAfter, you're capturing a copy of self at the point that it's called. You then keep calling it with new copies of that original value. None of these are related to the value that ContentView holds (since they're copies, not references). If you want to share state, you need a reference type (a class).

mutating means "replaces the target value with a brand new, mutated value." It doesn't mean "copies of this value will be updated." A struct is a value type, not a reference type.



Related Topics



Leave a reply



Submit