Copies of arrays
Let's go back to the row of lockers. What happens when a program says var b = a? Picture a second row being built next to the first one, with the same items placed in it. From then on each row lives on its own: if you put something in the second row, the first one does not change.
Let's see what this looks like in code:
let a = [1, 2, 3]
var b = a
b.append(4)
print(a) // [1, 2, 3]
print(b) // [1, 2, 3, 4]Let's go through the program line by line:
let a = [1, 2, 3]builds the first row.var b = abuilds a second row with the same elements.aloses nothing: assignment copies the elements, it does not take them away.b.append(4)adds a locker to the second row only.aprints as before,[1, 2, 3], andbprints with the new element.
Note that a is declared with let, and b with var. The copy has its own declaration, so b can change even though a is a constant.
An array is a value type
A type whose assignment and passing to a function create an independent copy is called a value type. Arrays in Swift are a value type, like Int and String. After var y = x, nobody expects a new value in y to change the number in x. Arrays work the same way.
Press "Next step" and compare a with b, and then with the arrays inside the function: only the array that append was applied to changes.
1func withBonus(_ scores: [Int]) -> [Int] {2 var result = scores3 result.append(100)4 return result5}6 7let a = [1, 2, 3]8var b = a9b.append(4)10print(a)11print(b)12let c = withBonus(a)13print(a)14print(c)
The constant a is the first row, with three elements.
A function gets its own copy of an array too. In the example, withBonus receives a in the parameter scores and changes not that but the copy result, because a parameter is a constant. After the call, a is the same as before.
Interviewers often ask: what happens to the first array if you change the second? In Swift, nothing: an array is a value type. They ask because in many other languages, such as JavaScript and Python, both names would lead to one and the same array.
When Swift copies the elements
Copying a big array on every assignment would take a long time, so Swift does not do it right away. As long as neither array has been changed, they use the same elements. Right before the first change to one of them, Swift copies the elements for it. This technique is called copy-on-write.
Here the two-row picture is inaccurate. The second row is built not at the moment of var b = a, but when something in one of the rows changes for the first time. But you cannot notice this from how the program works: arrays always behave as if the copy were made right away.
Common mistake
A function trying to add an element to the array it was passed:
func addScore(_ list: [Int]) {
list.append(5) // ✗
}error: cannot use mutating member on immutable value: 'list' is a 'let' constantThe program does not compile: the parameter is a constant holding a copy of the array. And even if it were a variable, only the copy inside the function would change. For a function to change the caller's array, you declare the parameter as inout and put & at the call:
func addScore(_ list: inout [Int]) {
list.append(5)
}
addScore(&b)
print(b) // [1, 2, 3, 4, 5]Key points
- An array is a value type:
var b = acreates an independent copy, and changes tobare not seen ina. - Assignment does not empty the original array, and the copy has its own declaration: an array from a
letcan be copied into avarand changed. - A function gets a copy of an array and can change the caller's array only through
inoutand&. - Swift copies the elements right before the first change, but the program behaves as if the copy were made right away.