Skip to content
Hironobu Iga

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.