On ArraySlice in Swift
Why slicing an Array hands you an ArraySlice rather than an Array — it is a view onto the original storage, not a copy.
- Published
This article is also published elsewhere. https://iganin.hatenablog.com/entry/2019/02/15/012024
Originally written in Japanese. This is a translation of the same piece.
Introduction
Slice an Array in Swift and what comes back is an ArraySlice, not an Array. I wanted to know why it is
not an Array, and what ArraySlice is actually for, so I looked into it.
Environment
- Xcode 10.0
- Swift 4.2
About ArraySlice
Apple’s documentation says:
The ArraySlice type makes it fast and efficient for you to perform operations on sections of a larger array. Instead of copying over the elements of a slice to new storage, an ArraySlice instance presents a view onto the storage of a larger array. And because ArraySlice presents the same interface as Array, you can generally perform the same operations on a slice as you could on the original array.
For more information about using arrays, see Array and ContiguousArray, with which ArraySlice shares most properties and methods.
ArraySlice - Swift Standard Library | Apple Developer Documentation
Reduced to the essentials: rather than allocating new storage and copying the array’s elements into it, an
ArraySlice represents a view onto the array. And because it presents the same interface as Array, you can
generally do to a slice whatever you would do to the array.
So an ArraySlice allocates no new memory. It holds a reference to the original array along with a start index
and a count.
Checking it in code
To confirm this I printed the pointer addresses. prefix is used here for clarity. The pointers turn out to be
identical, so a and its slice b point at the same address.
let a = [1, 2, 3, 4, 5]
var b = a.prefix(3)
print(UnsafePointer(a))
b.withUnsafeBufferPointer { (pointer) -> Void in
print(pointer)
}
0x00006000011fcac0
UnsafeBufferPointer(start: 0x00006000011fcac0, count: 3)
I also checked what happens on assignment. If only a reference is held, does the original array’s value change too? It does not — new storage is allocated at the moment the value is modified.
let a = [1, 2, 3, 4, 5]
var b = a.prefix(3)
print(UnsafePointer(a))
b.withUnsafeBufferPointer { (pointer) -> Void in
print(pointer)
}
b[2] = 9
b.withUnsafeBufferPointer { (pointer) -> Void in
print(pointer)
}
0x00006000010e0de0
UnsafeBufferPointer(start: 0x00006000010e0de0, count: 3)
UnsafeBufferPointer(start: 0x00006000026f2620, count: 3)
This is the same behaviour as copy-on-write in a struct: storage is allocated when the value changes, not when it is assigned to a variable.
let a = [1, 2, 3, 4, 5]
var c = a
print(UnsafePointer(a))
print(UnsafePointer(c))
c[0] = 1
print(UnsafePointer(c))
0x0000600000622500
0x0000600000622500
0x00006000006360a0
Why use ArraySlice
The main reason is presumably performance. Holding a reference to the original data, plus only the information about which part of it to look at, intuitively seems faster than allocating new memory and copying the array’s elements into it.
Finally
Getting an ArraySlice back means you cannot pass it to methods declared over Array without converting it
first, and I had honestly been thinking it would be nicer if slicing just returned an Array. Having looked
into it, I changed my mind: ArraySlice earns its place.