Mutating the properties of a struct

Is there a case where mutating the properties of a struct is useful? I'm curious about an example!

1 Like

Yes? Or am I missing something from your question?
An easy example I used in a real project was some sort of data collector that was incrementally updated in delegate callbacks triggered by the UI (still using UIKit, so basically child view controllers created incremental data and their parent view controller had a state struct to sequentially collect that). This was the easiest way to do that was then also thread safe (as I passed the struct to a background queue once it was "full", even before async/await and Sendable).

If you want to see that in code let me know, but I don't want to type that all out here if my above description helps you enough. :smile:

1 Like

Thank you for your quick answer!

For me, struct and class have different uses.
And the advantage of struct is call by value.
But if you need to make the struct mutating
It seemed like I had to use a class.

It's decently rare that I absolutely have to use a var on a struct, but one thing I encountered in the past few weeks is dealing with time-limited access tokens for external APIs. The program I built is intended to be arbitrarily long-running but access tokens expire after 10 minutes, so I used a var and refresh the token when I need to make a new request to the API and the token's expired.

Another use I can think of off the top of my head is to cache expensive operations (encryption, encoding, network requests, etc.) where needed. You can either use a lazy var or do it yourself - if the optional cache storage property is nil then run the expensive operation, otherwise just return the cached result.

You're welcome! :smiley:

@MPLewis: Yup, now that you mention it I remember having done something similar. My tokens in fact had a much longer life-time, but I used a similar approach anyway.


As an aside, allow me to provide some commentary regarding this statement. I can understand that this might be the impression (especially, but not exclusively, for beginners), but I think that's a dangerous way to think/design things. The major difference between reference and value types (esp. when considering "what could bite me in the butt the hardest?") is probably shareability.

Reference types can be shared among different places in your code and this is not only a problem when writing asynchronous stuff. While it can be a useful feature (some things you want to share), the cost is that you increase the scope of thinking, i.e. the cognitive effort you have to invest when (re-)understanding your code (or someone else's).

Swift has a strong emphasis on code that you can reason about locally. If you read a function that gets a value-type parameter passed you typically do not have to think about where that came from (and whether it might be mutated while you are using it further down the line). Equivalently, if you pass it to yet another function you don't care what it does with this, your local copy stays unchanged.

So if mutability was only achieved by using reference types, you would lose ability to locally reason about code every time you "give" your instance to some other piece of code. All just because it was convenient to quickly mutate a single property of your type.


Lastly, I remembered yet another example that also combined mutability of a struct's property with access modifiers: private(set) is a thing. :smiley:

I had a struct that, among other things, could also be instantiated from a specific json payload (i.e. it was Codable, but in a specific case decoding wasn't just straight forward). This specific decoding involved having a list of items typed to this struct, but during decoding I had to modify each item.

So instead of writing a different decoding function I just added changed the one affected attribute with to be private(set) var and then added a simple function to decode this specific json payload. In this function, I decoded the list of items "in the regular way" (a one-liner), and then iterated over all the items and mutated their settable attribute (all in the same file/type definition, of course). Outside from this file, the struct seemed to have only immutable properties and looked "like before", but doing this was way easier than to manually define keys, do decoding by hand for this specific payload.

2 Likes

I believe this is a matter of (in)convenience and speed (mutating version tends to be slightly faster). Otherwise the foo and bar in the following example are equivalent:

struct Foo {
    var x, y, z: Int
}

struct Bar {
    let x, y, z: Int
}

var foo = Foo(x: 1, y: 2, z: 3)
...
foo.x = 4

var bar = Bar(x: 1, y: 2, z: 3)
...
bar = Bar(x: 4, y: bar.y, z: bar.z)

Note that with enums you only have the immutable (bar) behaviour:

enum Baz {
    case somethingElse
    case value(x: Int, y: Int, z: Int)
}

var baz: Baz = .value(x: 1, y: 2, z: 3)
...
baz.value.x = 4 // 🛑 oops, not quite possible in current swift
guard case let .value(v) = baz else { fatalError() }
baz = .value(x: 4, y: v.y, z: v.z)
1 Like

I assume use cases like arrays / containers would be mutating properties

To observe a state of something, struct + didSet or Publisher is a good combo I guess.

1 Like

https://developer.apple.com/documentation/swiftui/dynamicproperty

1 Like

FWIW, I mark all my struct properties var unless there’s an invariant that must be maintained.

6 Likes

This.

If a struct has a memberwise initializer that

  • has the same access control of the properties
  • doesn't enforce particular invariants

then the properties should always be declared as var, and that's because if they weren't, I would still be able to reassign an instance of that struct to a variable by initializing a new one with all the same values of the properties but one.

For example, this piece of code

struct Person {
  var name: String
  var age: Int
}

var p = Person(name: "Foo", age: 42)
p.age += 1

is (semantically) identical to this

struct Person {
  let name: String
  let age: Int
}

var p = Person(name: "Foo", age: 42)
p = Person(name: p.name, age: p.age + 1)

the second example makes no sense, and doesn't take advantage of Swift value semantics.

The key difference from a mutable class is the following:

struct PersonStruct {
  var name: String
  var age: Int
}

let ps = Person(name: "Foo", age: 42) // assigned to a let constant
ps.age += 1 // this is not allowed and doesn't compile

class PersonClass {
  var name: String
  var age: Int

  // it needs an explicit init
}

let pc = Person(name: "Foo", age: 42) // assigned to a let constant
pc.age += 1 // this works and compiles just fine

To me, thinking that "mutable properties" means "class" shows a fundamental misunderstanding of Swift value semantics.

5 Likes

I don't think it's so fundamental as I can imagine Swift version where struct values are immutable, or another one where enum values are mutable.

I can imagine that too, but it would be a very boring version of Swift: the fact that you can have mutable properties on a value type is one of the best features of the language to me, and not taking advantage of that feature seems, to me, mostly caused by a misunderstanding.

1 Like