Swift Talk #21

## Structs and Mutation

### This episode is freely available thanks to the support of our subscribers

We can change structs by mutation, functional chaining, and inout parameters. We discuss how they differ at the call site and why they’re all equivalent.

00:00 Let's talk about structs and mutation today. 00:12 We'll discuss two topics: how mutation on structs differs from mutation on objects, and how can we achieve mutation on structs. 00:22 Mutation with objects sounds a bit dangerous; if you have objects and you mutate them somewhere in your app (for example, on a different queue), they can change other parts of your app as well. 00:44 If you have one object and many variables pointing to it, every value of every variable will change. 00:51 You can even run into race conditions, so you have to be very careful when mutating objects. Yet it's very useful, because often you want sharing — for example, with a UIScreen.

## Mutation

01:05 The functional programming world said: "We think mutation is bad, so we'll make everything immutable," which is a very different approach. Structs in Swift hit the sweet spot, because you can mutate a struct, but you don't have global side effects. 01:22 Mutating structs is very different from mutating objects, in that mutating a struct only changes a single variable and not all variables with the same value. 01:34 As an example, here we have an array, `x`. If we want to sort it, we can just call `x.sort()`, which will mutate the array in place:

``````var x = [3, 1, 2]
x.sort()
``````

01:50 Because it's a mutating method, we need to define `x` as `var`. Otherwise, we can't even call `sort`. After calling `sort`, `x` has a new value. A mutating method on a struct only changes a single variable; if we would've created a copy of `x`, then calling the mutating method on `x` wouldn't have changed the copy:

``````var x = [3, 1, 2]
let y = x
x.sort()
x // [1, 2, 3]
y // [3, 1, 2]
``````

02:26 The value of `x` changes within its scope — for example, it changes within a function body — but it doesn't change anything outside of its scope. We could also make the code a bit more elaborate and loop over `x.indices` and square each value within the loop's body. Even though we're mutating `x`, we don't change `y`:

``````var x = [3, 1, 2]
let y = x
x.sort()
for idx in x.indices {
x[idx] *= x[idx]
}
y // [3, 1, 2]
``````

## An Immutable `sort`

03:16 There's another approach to solve the same problem (sorting and squaring). We can use a different version of `sort`, called `sorted`. This method doesn't mutate the array in place, but it returns a new, sorted array. We can verify that `y` is unchanged, but the return value is now sorted. We can easily chain these non-mutating methods together and continue to do calculations. 04:13 We can square using `map` and keep on composing. Composing things by chaining method calls is very different from writing the mutating version:

``````y.sorted().map { \$0 * \$0 }
``````

04:24 In the end, both versions are equivalent, so it's mostly a matter of taste: which version makes your code more readable? It's hard to say that one is better than the other. It depends on what you're doing, and then you can decide on the nicest API. 04:54 In this case, the immutable variant is more readable because it's more compact. If we wanted to implement an in-place quicksort, however, the mutable version would be more readable (and possibly the only way to implement the quicksort).

05:08 Let's look at writing mutating methods. We'll start with an `Account` struct, which has a `balance` property. We'll add a `deposit` method, which has an `amount` parameter. The `deposit` method returns a new, updated `Account` value:

``````struct Account {
let balance: Int

func deposit(amount: Int) -> Account {
return Account(balance: balance + amount)
}
}
``````

05:57 This `deposit` method is similar to `sorted` because it doesn't change the original value, but instead returns a new value. We'll change the name to `depositing` to reflect that. Now we can use it:

``````struct Account {
let balance: Int

func depositing(amount: Int) -> Account {
return Account(balance: balance + amount)
}
}

let account = Account(balance: 0)
account.depositing(amount: 100)
``````

## A `mutating` Variant

06:29 The above is one way of implementing `depositing`. Functional programmers like this style: just keep returning new values. A different way would be to create a `mutating` variant. We can just add a new method, `deposit`, that's marked as `mutating`. Because it's `mutating`, it doesn't need to return a new value:

``````struct Account {
var balance: Int

func depositing(amount: Int) -> Account {
return Account(balance: balance + amount)
}

mutating func deposit(amount: Int) {
balance += amount
}
}
``````

07:19 To make this work, we also need to change the property declaration of `balance` to a `var`, because if we declare it as a `let`, we can never change it again. Writing the property as `var`, rather than `let`, might feel a bit impure, but we can still control mutability through the variable that points to the account. For example, we can't call `account.deposit`:

``````let account = Account(balance: 0)
account.deposit(amount: 100) // error
``````

07:57 The compiler will give us an error, because we can't call a mutating method on a variable that's declared with `let`; we have to change it to `var`. Now, let's have a look at the result of `account.balance`:

``````var account = Account(balance: 0)
account.depositing(amount: 100)
account.deposit(amount: 10)
account.balance // 10
``````

08:26 The call to `depositing()` doesn't change the variable, because it returns a new value.

08:42 The `deposit` method changes the value of the variable. If we would've created a different account, then calling `deposit` on one variable wouldn't change the other variable. We can say that a mutating method on a struct is safer than a method that changes an object. Because it doesn't have these global side effects, it only changes a single variable.

## Equivalence

09:15 We can see that both approaches are equivalent: we could write the mutating version in terms of the non-mutating version, and vice versa. For example:

``````struct Account {
var balance: Int

func depositing(amount: Int) -> Account {
return Account(balance: balance + amount)
}

mutating func deposit(amount: Int) {
self = depositing(amount: amount)
}
}
``````

10:03 And here it is the other way around:

``````struct Account {
var balance: Int

func depositing(amount: Int) -> Account {
var copy = self
copy.deposit(amount: amount)
return copy
}

mutating func deposit(amount: Int) {
balance += amount
}
}
``````

10:29 We can call the mutating method because `copy` is declared as a `var`. Because `Account` is a struct, it actually makes a copy and `copy` is now an independent variable.

10:41 In a way, both methods do the same thing. They behave slightly differently, but once you have one, you can always declare the other.

## The `inout` Keyword

10:52 In addition, there's another related keyword. `mutating` works on methods, but we can use `inout` for function parameters. This also sounds a bit dangerous, because it might remind you of passing a reference, but `mutating` and `inout` are actually the same thing.

11:14 We can write `deposit` in a free function and pass in the account as an `inout` parameter. We can then freely mutate it within the body of the function:

``````func deposit(amount: Int, into account: inout Account) {
account.balance += amount
}
``````

12:01 `inout` is basically the same thing as `mutating` is for `self`: it allows you to mutate the value that you get passed in.

12:16 We can call our new function, and because the parameter is `inout`, we need to prefix it with an ampersand. It looks like we're dealing with pointers, but it's different. When you declare an `inout` parameter, the value gets copied into the function. Within the function we can change it, and then it gets copied back out when the function is done. It's not a mutable pointer, because within the function, you're working with your own, independent copy:

``````var account = Account(balance: 0)
deposit(amount: 10, into: &account)
``````

13:32 We can also use `inout` and `mutating` at the same time. For example, if we want to transfer money from one account into another, we can write a mutating method, which takes an `inout` parameter as well:

``````struct Account {
var balance: Int

mutating func transfer(amount: Int, from: inout Account) {
balance += amount
from.balance -= amount
}
}
``````

14:19 It's also easy to see that `inout` means copy-in copy-out. You can't dispatch to another thread and change an `inout` parameter (because that would let the `inout` parameter escape).

14:53 Depending on what problem you're solving, you can choose your strategy. You can write immutable methods, you can write mutating methods, or you can use `inout` parameters. They're all equivalent. There might be small performance differences, but for most code, it doesn't matter. Generally, it's best to decide based on readability and choose the version that makes your code the clearest at the call site.

### Resources

• #### Playground

Written in Swift 3

### Swift, the Language

20 Episodes · 6h36min

See All
Experiments

### A First Look at SwiftUI

Episode 156 · Jun 14

Unlock Full Access

## Subscribe to Swift Talk

• ### Watch All Episodes

A new episode every week

Take Swift Talk with you when you're offline

• ### Support Us

With your help we can keep producing new episodes