Why constant constraints the property from a structure instance but not the class instance?

Structures in Swift are value types – and, semantically speaking, values (i.e ‘instances’ of value types) are immutable.

A mutation of a value type, be it through directly changing the value of a property, or through using a mutating method, is equivalent to just assigning a completely new value to the variable that holds it (plus any side effects the mutation triggered). Therefore the variable holding it needs to be a var. And this semantic is nicely showcased by the behaviour of property observers around value types, as iGodric points out.

So what this means is that you can think of this:

struct Foo {
    var bar = 23
    var baz = 59
}

// ...

let foo = Foo()
foo.bar = 7 // illegal

as doing this:

let foo = Foo()

var fooCopy = foo // temporary mutable copy of foo.

fooCopy.bar = 7   // mutate one or more of the of the properties

foo = fooCopy     // re-assign back to the original (illegal as foo is declared as
                  // a let constant)

And as you can clearly see – this code is illegal. You cannot assign fooCopy back to foo – as it’s a let constant. Hence, you cannot change the property of a value type that is declared as a let, and would therefore need make it a var.

(It’s worth noting that the compiler doesn’t actually go through this palaver; it can mutate the properties of structures directly, which can be seen by looking at the SIL generated. This doesn’t change the semantics of value types though.)


The reason you can change a mutable property of a let constant class instance, is due to the fact that classes are reference types. Therefore being a let constant only ensures that the reference stays the same. Mutating their properties doesn’t in any way affect your reference to them – you’re still referring to the same location in memory.

You can think of a reference type like a signpost, therefore code like this:

class Foo {
    var bar = 23
    var baz = 59
}

// ...

let referenceToFoo = Foo()

you can think of the memory representation like this:

|    referenceToFoo     |  --->  | Underlying Foo instance |
| (a reference to 0x2A) |        |<----------------------->|
                                 |0x2A       |0x32         |0x3A
                                 |  bar: Int |  baz : Int  |
                                 |     23    |      59     |

And when you mutate a property:

referenceToFoo.bar = 203

The reference (referenceToFoo) itself isn’t affected – you’re still pointing to the same location in memory. It’s the property of the underlying instance that’s changed (meaning the underlying instance was mutated):

|    referenceToFoo     |  --->  | Underlying Foo instance |
| (a reference to 0x2A) |        |<----------------------->|
                                 |0x2A       |0x32         |0x3A
                                 |  bar: Int |  baz : Int  |
                                 |    203    |      59     |

Only when you attempt to assign a new reference to referenceToFoo will the compiler give you an error, as you’re attempting to mutate the reference itself:

// attempt to assign a new reference to a new Foo instance to referenceToFoo.
// will produce a compiler error, as referenceToFoo is declared as a let constant.
referenceToFoo = Foo()

You would therefore need to make referenceToFoo a var in order to make this assignment legal.

Leave a Comment