What does copying a struct that holds a class reference, or building an array with Array(repeating:), actually share?

27.5.1 · L2 · Interview

Predict first

What does this print?

final class Box { var n = 0 }
struct Holder { var box = Box(); var tags = ["a"] }
var h1 = Holder()
var h2 = h1
h2.box.n = 7
h2.tags.append("b")
print(h1.box.n, h1.tags, h2.tags)

let slots = Array(repeating: Box(), count: 3)
slots[0].n = 5
print(slots.map(\.n))
let distinct = (0..<3).map { _ in Box() }
distinct[0].n = 5
print(distinct.map(\.n))

var xs = [1, 2, 3]
let ys = xs
xs[0] = 99
print(xs, ys)
var grid = Array(repeating: Array(repeating: 0, count: 2), count: 2)
grid[0][0] = 1
print(grid)
  • 7 ["a"] ["a", "b"] / [5, 5, 5] / [5, 0, 0] / [99, 2, 3] [1, 2, 3] / [[1, 0], [0, 0]]
  • 0 ["a"] ["a", "b"] / [5, 0, 0] / [5, 0, 0] / [99, 2, 3] [1, 2, 3] / [[1, 0], [1, 0]]
  • 7 ["a", "b"] ["a", "b"] / [5, 5, 5] / [5, 5, 5] / [99, 2, 3] [99, 2, 3] / [[1, 0], [0, 0]]

Mental model. Photocopying a folder of papers copies the papers, but a folder containing a key to a locker copies only the key.

Assigning a struct copies its stored properties one by one. An Array property is copied lazily (copy-on-write), so appending to h2.tags leaves h1.tags alone. A class-reference property copies only the reference, so h2.box.n = 7 changes the object h1 also points to. The struct is a value type in name only once it contains a reference.

Array(repeating: Box(), count: 3) evaluates Box() once and stores the same reference three times, so the first write shows up in all three slots ([5, 5, 5]); (0..<3).map { _ in Box() } creates three objects. Arrays of arrays are different: each inner array is a value, so grid[0][0] = 1 changes one cell only. Fix shared references with a copy method or by making the type a struct, and use isKnownUniquelyReferenced inside your own copy-on-write wrappers.

The class reference is shared, repeating: evaluates its element once, and arrays are independent values.

Common traps

  • Assuming a struct is deeply copied. Only value-type members are independent.
  • Filling an array of objects with repeating:. You get one object many times.

They might follow up with

  • How do you give a struct with a reference value semantics? Copy the object before mutating when it is not uniquely referenced (Q150).

Related questions

  • 1.1.7 What is copy-on-write? Which types use it?
  • 3.5.1 How do you implement copy-on-write for your own struct?
  • 27.5.2 How do dictionary default subscripts and nil assignment behave when you group and count, and why is removing…

How do dictionary default subscripts and nil assignment behave when… →