Learning Swift
Use Swift Struct For Total Immutability
The many ways to prevent mutability of Swift’s struct
In Choosing Between Structures and Classes , the document provides us some guides of when to use Class and when to use Struct.
The reasons to use Struct given as are below.
- Use Structures When You Don’t Control Identity
- Use Structures and Protocols to Model Inheritance and Share Behavior
While the above is true, nothing is mentioned about the great benefits I discovered about Struct, with its ability to ensure true immutable.
I’m amazed how thorough Struct are being protected from mutating when it is let
. Let’s check it out.
Member VAR of a LET struct cannot be altered externally
In class, if an object is let
, we can still alter the member variable that is var
, as shown below.
class A { var value = 10 }
let a = A()
a.value = 20 // Okay, can still mutate it even a is let
It’s the because class are reference type, only the memory location is immutable, but the content mutability is independent from it.
However, for struct, it is an error.
struct A { var value = 10 }
let a = A()
a.value = 20 // Can't assign to property: 'a' is a 'let' constant
Copy by Value prevent accidental change
For class, when it is assigned to a new object, it is changed by reference. Altering the new copy object member variable will also change the original object member variable.
class Aclass { var value = 0 }let classA = Aclass()
var classB = classA
classB.value = 1
print("\(classA.value) \(classB.value)") // result is 1 1
Do note for the snippet above, the classA.value
has been altered to 1 even though we just change classB.value
.
For struct, when assigned to a new object, an entirely new object is created instead. So alter the new object will have no impact on the original object.
struct Astruct { var value = 0 }let structA = Astruct()
var structB = structA
structB.value = 1
print("\(structA.value) \(structB.value)") // result is 0 1
Do note for the snippet above, the structA.value
is remained as 0 even though we just change structB.value
.
The below diagram helps illustrate this better

Member VAR can only be altered by internal mutating function
For a struct that has a function to change its own variable, the below will error out with the message “Left side of mutating operator isn’t mutable: ‘self’ is immutable”
struct A {
var value = 10 func changeValue() {
value += 10 // Error
}
}
To fix it, we will need to add mutating
keyword to it.
struct A {
var value = 10 mutating func changeValue() {
value += 10
}
}
Note: a class doesn’t need a
mutating
keyword for function that alter internal variable
This also applies to internal functions that calls the changeValue
functions, where a mutating
keyword is required to define the function.
struct A {
var value = 10 mutating func changeValue() {
value += 10
} mutating func callChangeValue() {
changeValue()
}
}
How does this helps ensure immutability?
The mutating
keyword is used for the compiler to easily know that a function is changing the internal variable, hence it is only allowed to be called by a var
struct variable, but not let
struct variable.
struct A {
var value = 10
mutating func changeValue() { value += 10 }
}var x = A()
x.changeValue()let y = A()
y.changeValue() // Error
The above snippet, the error on y.changeValue
is “Cannot use mutating member on immutable value: ‘y’ is a ‘let’ constant”.
By preventing let
struct calling the mutating
function, this helps ensure the struct doesn’t get mutated.
Lazy is considered mutating
Look at the below code snippet. When the struct is initialized, the lazyBool
is not initialized yet. It will only be initialized upon the first use of it.
struct LazyStruct {
lazy var lazyBool: Bool = { false } ()
func accessLazyBool() -> Bool {
return lazyBool // Error
}
}let a = LazyStruct()
a.lazyBool // Error
Therefore, it is considered an (internally) mutable variable. This cause the above code to generate the error “Cannot use mutating getter on immutable value: ‘self’ is immutable”
Note: this issue will not happen if this is Class instead of Struct
To fix this issue, 2 days
1. Remove the lazy
struct LazyStruct {
var lazyBool: Bool = { false } ()
func accessLazyBool() -> Bool {
return lazyBool
}
}let a = LazyStruct()
a.lazyBool
By not being lazy, this means the mutability of the variable is handled by the setter. Hence it can be controlled from there. The getter side of it is safe, and not an issue.
2. Change to mutating function or variable
struct LazyStruct {
lazy var lazyBool: Bool = { false } ()
mutating func accessLazyBool() -> Bool {
return lazyBool
}
}var a = LazyStruct()
a.lazyBool
Here we retain the lazy
keyword. But we fix it by making our function mutating
and also making the variable var
.
Capture self-value in escaping closure during construction
An escaping closure is like a function variable that can be performed at a later time.
If we are sending some self
value into it, that will risk the closure behave differently upon its execution at a later time, which is immutable
struct Astruct {
var value: Int = 11 // result is regardless of var or let
lazy var closure: () -> () = { // Error
print("\(self.value)")
}
}
The error states “Escaping closure captures mutating ‘self’ parameter”.
Note: this will not be an issue for a class too.
To prevent this issue, we’ll have to capture the value during construction, instead of accessing self
in the closure itself. The capturing is done using []
as shown in the code below
struct Astruct {
var value: Int = 11. // result is regardless of var or let
lazy var closure: () -> () = {
[self] in print("\(self.value)")
}
}
Optionally, you can also capture the value itself as below.
struct Astruct {
let value: Int = 11
lazy var closure: () -> () = {
[value] in print("\(value)")
}
}
As this value is captured during construction, it is more deterministic and considered immutable.
Self capturing differences of class and struct
Self capturing not only works for Struct, but also for Class. They seem to behave differently although technically it’s still the expected behavior.
If I have the below set of code
struct Foo {
var bar: Bool
init(bar: Bool) { self.bar = bar } mutating func doSomething() {
print("Bar was \(self.bar)")
let closure = { [self, oriBar = bar] in
if originalBar != self.bar {
print("Bar has changed \(oriBar) \(self.bar)")
} else
print("Bar didn't change \(oriBar) \(self.bar)")
}
}
self.bar.toggle()
print("Bar is \(self.bar)")
closure()
}
}var foo = Foo(bar: true)
foo.doSomething()
foo.doSomething()
The result will be as below
Bar was true
Bar is false
Bar didn't change true true
Bar was false
Bar is true
Bar didn't change false false
You’ll notice that for Struct, the below self
captured is done during the construction. Hence the self.bar
returns the boolean value at the time of closure
construction.
let closure = { [self, oriBar = bar] in
if originalBar != self.bar {
print("Bar has changed \(oriBar) \(self.bar)")
} else
print("Bar didn't change \(oriBar) \(self.bar)")
}
}
However, when we change to class
class Foo {
var bar: Bool
init(bar: Bool) { self.bar = bar } func doSomething() {
print("Bar was \(self.bar)")
let closure = { [self, oriBar = bar] in
if originalBar != self.bar {
print("Bar has changed \(oriBar) \(self.bar)")
} else {
print("Bar didn't change \(oriBar) \(self.bar)")
}
}
self.bar.toggle()
print("Bar is \(self.bar)")
closure()
}
}var foo = Foo(bar: true)
foo.doSomething()
foo.doSomething()
The result as below
Bar was true
Bar is false
Bar has changed true false
Bar was false
Bar is true
Bar has changed false true
For Class, it looks as if the below self
captured is only done during the execution of construction
, hence the self.bar
returns the boolean after self.bar.toggle()
i.e. opposite from when it is set.
let closure = { [self, oriBar = bar] in
if originalBar != self.bar {
print("Bar has changed \(oriBar) \(self.bar)")
} else
print("Bar didn't change \(oriBar) \(self.bar)")
}
}
In reality, for both Struct and Class, they are still being captured during construction of the closure
when the [self]
has been used.
The only difference is, Struct is the value type and Class is the reference type. Hence during construction,
- for Struct, a unique copy of
self
is created with an independent booleanbar.
Any changes to it only applies to the copied overself
value. - for Class, a reference copy of
self
is created referring to the same Booleanbar
. Any changes to it, change to the originalself
and the copied overself
value.
The code sample credits to the below article
Getter properties can have access to self
Previously we show the below
struct Astruct {
var value: Int = 11 // result is regardless of var or let
lazy var closure: () -> () = { // Error
print("\(self.value)")
}
}
The error states “Escaping closure captures mutating ‘self’ parameter”.
But if I do a minor modification as below
struct Astruct {
var value: Int = 11 // result is regardless of var or let
var closure: () -> () {
{ print("\(self.value)") }
}
}
No more error!! Why? Do we allow to pass in self
into a closure without first capturing it?
The main difference between the two is as below
lazy var closure: () -> () = { // Error
print("\(self.value)")
}
and
var closure: () -> () {
{ print("\(self.value)") }
}
The first is really just a variable closure, where a fixed closure i.e. { print(“\(self.value)”) }
is set when it is constructed. The =
is the key to indicate that.
The second is a getter variable (i.e. there’s no =
, but it is surrounded by {}
), where each time it is called, it will generate a new closure { print(“\(self.value)”) }
. Therefore it is allowed to use self.value
, since upon a new closure is generated, it will get the self.value
of that moment.
Hopes this clarifies the difference, and avoids confusion. The Struct is still protecting the immutability perfectly.
With the above sharing, we can see how well the Struct protect it’s immutability, to ensure nothing within it really changed, either internally or externally.
Feel free to share with me if you have more insight into how Struct enhances the immutability that is not covered above. Thanks!