Tutorial outline

Unit 1

  1. 01Getting started
  2. 02Names and functions
  3. 03Control
  4. 04Higher-order functions
  5. 05Environments and closures
  6. 06Functional abstraction
  7. 07Recursion
  8. 08Tree recursion

Unit 2

  1. 09Sequences
  2. 10Dictionaries, sets, and tuples
  3. 11Optionals
  4. 12Structures and enumerations
  5. 13Linked lists and trees
  6. 14Mutation and value semantics
  7. 15Classes and inheritance
  8. 16Protocols and generics
  9. 17Iterators and lazy sequences
  10. 18Efficiency and memoization

Unit 3

  1. 19Algebraic data types and pattern matching
  2. 20Writing an interpreter
  3. 21Error handling
  4. 22Concurrency with async and await
  5. 23Testing
  6. 24Building a Swift package

Mutation and value semantics

Changing data in place: why Swift arrays and structures are copied instead of shared, mutating methods, inout parameters, and how that differs from Python’s lists.

CS61A
Mutation · Composing Programs 2.4

CS61A’s Mutation lecture is full of surprises like this one, in Python:

>>> a = [1, 2]
>>> b = a
>>> b.append(3)
>>> a
[1, 2, 3]

a and b are two names for the same list, so changing it through one name changes what the other sees. This is called aliasing, and it causes a large share of bugs in Python programs.

Swift’s arrays, dictionaries, strings, and structures work differently.

Value semantics

What would Swift print?

var a = [1, 2]
var b = a
b.append(3)
print(a, b)

Type Error if running it would crash. For several lines of output, put each on its own line.

Show answer and explanation

Answer[1, 2] [1, 2, 3]

In Swift, var b = a gives b its own copy of the array. Appending to b changes only b; a is still [1, 2]. This is the opposite of the Python example above.

A type has value semantics when assigning it, or passing it to a function, behaves like making a copy. Int, String, Array, Dictionary, Set, and every structure and enumeration you define have value semantics. Two variables never secretly share one of these.

Copying a large array sounds slow, and Swift avoids actually copying until it must. The two arrays share storage until one of them is changed, and only then is the copy made. This is called copy-on-write; it keeps the simple rule without the cost.

Nested values are copies too:

What would Swift print?

var grid = [[0, 0], [0, 0]]
var row = grid[0]
row[0] = 7
grid[1][1] = 5
print(grid, row)

Type Error if running it would crash. For several lines of output, put each on its own line.

Show answer and explanation

Answer[[0, 0], [0, 5]] [7, 0]

row is a copy of the first row, so changing it leaves grid alone. Changing grid[1][1] directly does change grid. In Python, row would have been an alias for the inner list, and grid would show the 7.

Where mutation is allowed

Because values are copied, Swift can check at compile time which code is allowed to change which values.

  • A name bound with let can never be changed, including its properties and elements.
  • A function’s parameters are constants. Changing an array argument inside a function is a compile-time error.

What would Swift print?

func addZero(_ values: [Int]) {
    values.append(0)
}
var numbers = [1]
addZero(numbers)
print(numbers)

Type Error if running it would crash. For several lines of output, put each on its own line.

Show answer and explanation

AnswerError

values is a parameter, and parameters are constants: “cannot use mutating member on immutable value”. The function receives a copy it cannot change. To return a changed array, copy it into a var, change that, and return it.

mutating methods

A method that changes a structure’s properties must be marked mutating, and it can only be called on a var:

struct Counter {
    private(set) var count = 0

    mutating func increment() {
        count += 1
    }
}

var clicks = Counter()
clicks.increment()
clicks.increment()
print(clicks.count)   // 2

private(set) lets other code read count but not assign it, so the only way to change it is through increment(). Calling increment() on a let counter is a compile-time error: the mutating keyword is how the compiler knows the call would change it.

inout parameters

Sometimes a function’s job really is to change its caller’s variable. An inout parameter allows it, and the caller writes & to show the variable may be changed:

func double(_ values: inout [Int]) {
    for i in values.indices {
        values[i] *= 2
    }
}

var numbers = [1, 2, 3]
double(&numbers)
print(numbers)   // [2, 4, 6]

The & at the call site is the point: you can see, reading the call, that numbers is about to change. Mutation in Swift is always visible where it happens.

What would Swift print?

func swapTwo(_ x: inout Int, _ y: inout Int) {
    (x, y) = (y, x)
}
var a = 1
var b = 2
swapTwo(&a, &b)
print(a, b)

Type Error if running it would crash. For several lines of output, put each on its own line.

Show answer and explanation

Answer2 1

Both parameters are inout, so the function swaps the caller’s variables themselves. Without inout, it would swap its own copies and nothing would change.

Put the lines in order

Put the lines in order. The method must be mutating; the program prints 3.

  1. }
  2. count += 1
  3. mutating func increment() {
  4. counter.increment()
  5. print(counter.count)
  6. var counter = Counter()
  7. }
  8. for _ in 1...3 {
  9. var count = 0
  10. struct Counter {
  11. }

Show the correct program
struct Counter {
    var count = 0
    mutating func increment() {
        count += 1
    }
}
var counter = Counter()
for _ in 1...3 {
    counter.increment()
}
print(counter.count)

Mutation is still useful

None of this means mutation is bad. Building up a result in a var inside a function, as in every lab so far, is mutation, and it is perfectly safe: nothing outside the function can see the changes until the value is returned. What value semantics removes is surprising mutation, a change made in one place showing up in another.

Some things really should be shared: a bank account, a network connection, a game’s single world. For those, Swift has classes, the topic of the next lesson.

Lab 13: Mutation

Q1: Remove the odd numbers

Write removeOdd(_:), which removes every odd number from an array in place.

func removeOdd(_ values: inout [Int]) {
    // your code here
}
Show solution
func removeOdd(_ values: inout [Int]) {
    values.removeAll { $0 % 2 != 0 }
}

var numbers = [1, 2, 3, 4, 5, -3]
removeOdd(&numbers)
assert(numbers == [2, 4])

var empty: [Int] = []
removeOdd(&empty)
assert(empty == [])

removeAll(where:) removes every element for which the closure returns true. Testing != 0 rather than == 1 handles negative odd numbers, whose remainder is −1.

Q2: A bounded counter

Write a structure BoundedCounter with a limit set when it is created, a read-only count starting at 0, a mutating increment() that stops at the limit, and a mutating reset().

Show solution
struct BoundedCounter {
    let limit: Int
    private(set) var count = 0

    init(limit: Int) {
        self.limit = limit
    }

    mutating func increment() {
        count = min(count + 1, limit)
    }

    mutating func reset() {
        count = 0
    }
}

var counter = BoundedCounter(limit: 2)
counter.increment()
counter.increment()
counter.increment()
assert(counter.count == 2)

var copy = counter
copy.reset()
assert(copy.count == 0)
assert(counter.count == 2)   // the original is unaffected

The last two assertions show value semantics: resetting copy does not touch counter.

Q3: Rotate in place

Write rotateLeft(_:), which moves the first element of an array to the end, in place. An empty array is left unchanged.

func rotateLeft(_ values: inout [Int]) {
    // your code here
}
Show solution
func rotateLeft(_ values: inout [Int]) {
    guard !values.isEmpty else {
        return
    }
    let first = values.removeFirst()
    values.append(first)
}

var numbers = [1, 2, 3, 4]
rotateLeft(&numbers)
assert(numbers == [2, 3, 4, 1])
rotateLeft(&numbers)
assert(numbers == [3, 4, 1, 2])

var none: [Int] = []
rotateLeft(&none)
assert(none == [])

removeFirst() removes and returns the first element, and would stop the program on an empty array, which is why the guard comes first.

What’s next

Next lesson: classes, the reference types used when something should be shared.