Update, July 9: Apple has made big changes to Swift’s array implementation in the latest Xcode beta – happily, the descriptions below no longer apply.

In my last post I discussed Swift’s peculiar treatment of immutability in arrays. Below I’ll look at another unusual aspect of arrays – the ambiguous results you get when copying.

When a copy is not a copy

Unlike all other struct-based types in Swift, arrays don’t actually perform a copy when you assign one to another. Instead, they share the values of the initial array, even after manipulation of one or the other. Here, the copiedNames array isn’t actually copied, but still references the all the values of the original names array:

var names = ["Stuffy", "Chilly", "Hallie"]
let copiedNames = names

names[0] = "Lambie"                // changes to the first array
println(copiedNames)               // show up in the "copy"
> [Lambie, Chilly, Hallie]

copiedNames[2] = "Fabulous Fabio"  // and changes to the copy
println(names)                     // show up in the original
> [Lambie, Chilly, Fabulous Fabio]

Why is this happening? According to The Swift Programming Language,

Swift only performs an actual copy [of an array] behind the scenes when it is absolutely necessary to do so. Swift manages all value copying to ensure optimal performance, and you should not avoid assignment to try to preempt this optimization.

“Absolutely necessary”

If an assignment like let copiedNames = names doesn’t create a copy initially, when does a copy get made? Swift determines that a copy is necessary whenever an operation might modify the length of either array. If elements of one of the arrays are added or removed, the arrays are unlinked:

names += "Sir Kirby"              // adding an item
println(copiedNames)              // separates the arrays
> [Lambie, Chilly, Fabulous Fabio]

How do you know?

Here’s a riddle – take a look at the following code, and tell me what gets printed at the end. The swizzle() function takes an array and moves the first item to the end.

var cities = ["Minneapolis", "Atlanta", "Seattle", "Chicago"]
let originalCities = cities

cities = swizzle(cities)           // move the first item to the end
println(originalCities[0])
> ???

The answer is: It depends!

Depending on how swizzle() is implemented, the shared values may or may not be copied. This version of the function breaks the linkage, because it uses .append() and .removeAtIndex(), which modify the array’s length:

func swizzle(var arr: String[]) -> String[] {
    arr.append(arr.removeAtIndex(0))
    return arr
}

while this version keeps the sharing intact, since it only moves the values around:

func swizzle(arr: String[]) -> String[] {
    let first = arr[0]
    for i in 0..(arr.count - 1) {
        arr[i] = arr[i + 1]
    }
    arr[arr.count - 1] = first

    return arr
}

That’s bananas!

Look again: we’re calling a function with cities as a parameter, and depending on its implementation we may or may not be also changing the originalCities array. What?

Keep ‘em separated

If you need to test whether or not the arrays you’re working with are still sharing values, Swift provides the identity operator ===.

var numbers = [4, 8, 15, 16, 23, 42]
let copiedNumbers = numbers

println(numbers === copiedNumbers)
> true

numbers += 99                       // increase the size of original
println(numbers === copiedNumbers)
> false

If you need to manually separate your shared arrays, you can use the .unshare() method on an existing array, or copy your array with the source array’s .copy() method. Note that you can’t unshare a “constant” array, so plan ahead.

var numbers = [4, 8, 15, 16, 23, 42]
let copiedNumbers = numbers

println(numbers === copiedNumbers)
> true

numbers.unshare()                   // unshare the original variable
println(numbers === copiedNumbers)
> false

let anotherCopy = numbers.copy()
println(numbers === anotherCopy)
> false

I’m tempted to always copy() arrays, just so I have a reliably autonomous array. This ambiguity feels like a pretty big potential pitfall in what is otherwise a fairly safe language.