Revelation (?) about mutating struct methods

Since the first time I saw it I've been baffled by the need for the 'mutating' keyword. It very simple to understand the rule; but to me that's not the issue. The issue for me has always been why the keyword necessary at all. Especially since its not necessary for a class method that changes its own properties.

I just read the following, and while I don't think it states exactly what I am going to state, it helped clear it up for me: https://www.natashatherobot.com/mutating-functions-swift-structs/.

It seems to me that what it is really "saying" is not just that the method mutates one or more properties, but also that any variable having a mutating method invoked upon it must be declared with the var keyword, and not the let keyword. This is because (of course) a struct is a value type, and by declaring it with let you are stating that the object (variable) itself is immutable. Which is not the case for a reference type (a class).

Is it that simple? My concern up to this point had been that I thought perhaps the requirement for the keyword was meant to discourage its use. But now it seems that was probably just my interpretation because I didn't understand why it was required.

Hope everything I have said is valid and true, and may even help someone else.

Another reason is that it means it is modifying the whole object itself, not just one of its properties. This is because self is some kind of value type (like a struct) and modifying it basically means allocating/creating a copy of self + your changes.

So in some ways it may discourage its use. It can be expensive/slow to have a lot of mutations to an object and that may convince you to change to a reference type (like a class) instead. Although there are multiple ways to achieve the performance/efficiency that you need and these are just some of the options.

At a low level, is that really what happens? If I have an Int property and I mutate it, are you saying it creates a whole new struct object, just with a different value for the property? I find that hard to believe...

That's the semantic model but it can be optimised in many situations.

1 Like

In practice it usually optimizes to an in place mutation of the changed properties.

1 Like

Reading this document provides a great deal of insight:

1 Like

What are the requirements for the swift optimizer being able to optimize value-type mutations to be non-copying? The Ownership Manifesto mentions the moveonly declaration and Copyable protocol, but I don't fully understand everything in that document.

I'm assuming a fixed layout is required, which should be guaranteed naturally by using a swift struct. Then does it matter if you're modifying a reference vs value type? Are there other criteria that must be met?

If by fixed layout you mean that the order of the fields is never going to change while the program is running, then all Swift structs are fixed layout. They are not, however, guaranteed to be the same every time you compile the code unless you actually make them @_fixed_layout (which is use at your own risk at the moment).

I think the most important thing is that you make sure that you never access the original data instead of your copy in a mutating method or function with an inout argument.

struct Foo {
    var bar: Int
    var baz: Int
}

var qux = Foo(bar: 42, baz: 69105)

func doStuff(_ foo: inout Foo) {
    foo.bar = 10
    // Don't do this!
    print(qux.bar)
}

doStuff(&qux)

But that's why they added the memory exclusivity rule: just to make sure this can't happen.

Oddly, the 4.1 REPL prints 10 when (assuming this is even still allowed) it should be printing 42.

EDIT: I just tried compiling it, and it crashes due to an exclusivity violation. So, yeah, this is just REPL weirdness.

are you sure this is true? i hear this repeated a lot and it just doen’t make sense that class mutation would be intrinsically more efficient than struct mutation. i think the exclusivity rule is just a safety measure that comes from the write-back semantic model

Basically yes, but it was slightly more likely to happen in practice before the memory exclusivity rule.

I thought that’s how it was, partly because I’d seen it stated on blog posts/tutorials about deciding between structs/classes, but it appears I was mistaken and that the exclusivity rule is intended to prevent the behavior that I previously believed was commonplace.

You learn something new every day! :smile:

1 Like

Boy, all of this confusing is not giving me confidence of understanding the reality of the situation...

@Ponyboy47, can you restate explicitly your former belief and what you now believe to be the case?

Yes, I previously believed that mutating any value type (like a struct) would result in a new object being created to replace the old one (exensive/slow if you’re mutating a struct a lot). It has been proven to me that this is not the case, and that mutating a value type just does the mutation in-place.

I would just remove my earlier remark, but will leave it for historical reasons so anyone else who comes across this thread can see the discussion my misguided remark sparked.

Thanks a lot for making this correction! Your assertion had me stumped, so I am glad to hear that it is not actually the case.

1 Like