[swift unboxed]

Safely unboxing the Swift language & standard library


CollectionOfOne

Start digging into collection types in Swift starting with the simplest of them all: CollectionOfOne.

28 November 2016 ∙ Standard Library ∙ written by

It’s time to read some more source code! Today’s goal: understand collection types in Swift a little better.

I wanted to start with the simplest one in the standard library: CollectionOfOne. As the name suggests, this is a collection that holds a single element. Perhaps EmptyCollection is the simplest, but I wanted to look at a collection that holds some data. 🗃

CollectionOfOne.swift

As usual, you can see the full source file online, but the relevant bits will be right inline below.

Here’s where it all begins, with the type declaration:

public struct CollectionOfOne<Element> : MutableCollection, RandomAccessCollection {

CollectionOfOne is a struct that takes a generic type parameter to say what kind of element it holds. We’ll return to the protocols in a little bit.

internal var _element: Element

Here’s the data storage. If it were a “collection of zero or one”, we would use an optional here but remember, there will always be one Element, and a single scalar value does the trick.

There’s a single initializer:

public init(_ element: Element) {
  self._element = element
}

Very simple, it takes an Element and stores it to the property.

Thanks to type inference, you won’t need to specify the generic type parameter when you instantiate one of these:

// sample usage
let collection = CollectionOfOne(42)
// type is inferred as CollectionOfOne<Int>

And that’s it for initialization and storage.

So far, a collection that holds a single element doesn’t seem useful—why not use an array with a single element instead?

Collection Of One?

There are places in the standard library where the parameter is a collection and at the call site, you know you only have a single item to pass in. You can gain some efficiencies by using CollectionOfOne instead of a full-blown array.

You can search through the standard library code for some examples. One example is inserting an element into an array, which replaces a range in the array with a collection. And since you’re inserting a single element—that’s where CollectionOfOne comes in.

Going back to protocols now, CollectionOfOne conforms to two protocols: MutableCollection and RandomAccessCollection. Let’s take one protocol at a time.

Mutable Collection

According to the documentation, a collection is a “sequence whose elements can be traversed multiple times, nondestructively, and accessed by indexed subscript.”

The protocol hierarchy here looks like this:

Sequence > Collection > MutableCollection

Here’s the summary of each protocol:

  • Sequence means you can iterate through a sequence of elements.
  • Collection adds subscripts and guarantees a finite number of elements.
  • MutableCollection adds setters for things like subscripts to make the collection mutable.

Random Access Collection

Now that we have the basics of Collection, RandomAccessCollection adds a few more refinements to the type. It has a shorter protocol inheritance chain:

BidirectionalCollection > RandomAccessCollection

In a nutshell:

  • BidirectionalCollection means you can traverse the collection forwards and backwards.
  • RandomAccessCollection adds some stricter guarantees around performance; for example, measuring the distance between indices must take constant time.

Thanks to protocol extensions, many of the implementation details of these protocols is already handled for you. What’s left to implement?

Index Types

Since collections are accessible by index, we need to say what the index types are:

public typealias Index = Int
public typealias Indices = CountableRange<Int>

When accessing a single Index, the type is Int; for a range of Indices, the type is CountableRange<Int>.

You’ve probably accessed an array by subscript like myArray[2], where 2 is an Int. You can also access an array slice by providing a range like myArray[2...8], where 2...8 is a countable range.

For a collection of one though, the range is probably not as useful.

Indices

Now that we have the index types, it’s time to return some index values. Since this is a collection of one, these are super easy:

public var startIndex: Int {
  return 0
}

public var endIndex: Int {
  return 1
}

We’re starting at 0, which shouldn’t be too surprising. The end index is the position just past the end; that is, accessing the collection at subscript endIndex is out of bounds.

Next up, a little index math to find the next index:

public func index(after i: Int) -> Int {
  _precondition(i == startIndex)
  return endIndex
}

There’s only one valid index—0—in our collection of one. That means the only valid call to this function is index(after: 0) and the answer is the endIndex of 1. The precondition will cause a runtime trap if you ask for any other successor index.

Preconditions are like assertions, but they also fire in release builds. Assertions are usually for catching errors during debugging but are no-ops in release.

public func index(before i: Int) -> Int {
  _precondition(i == endIndex)
  return startIndex
}

Likewise, the only possible call to index(before:) is to pass in the endIndex and get the startIndex returned.

Subscript Access

With all this talk of indices, it’s time to use them to reference some data. That means using a subscript to get or set the element:

public subscript(position: Int) -> Element {
  get {
    _precondition(position == 0, "Index out of range")
    return _element
  }
  set {
    _precondition(position == 0, "Index out of range")
    _element = newValue
  }
}

Again, we’re using preconditions here to check for a valid index. This is super easy since the only valid index to get or set is 0, and all accesses are to the property _element.

Protocol Extensions

Collections also have a count property to say how many things are in the collection. Thanks to a protocol extension, you get this for free. We can peek inside Collection.swift for the default implementation:

// Inside the protocol extension, Collection.swift
public var count: IndexDistance {
  return distance(from: startIndex, to: endIndex)
}

That’s a reasonable implemenation that works for CollectionOfOne too. But since the count is a constant, providing a local implementation isn’t a bad idea:

// back in CollectionOfOne.swift
public var count: Int {
  return 1
}

Easy, right? 😄

More Protocols

There are two more protocols to conform to for debugging purposes:

extension CollectionOfOne : CustomDebugStringConvertible {
  public var debugDescription: String {
    return "CollectionOfOne(\(String(reflecting: _element)))"
  }
}

Everyone loves print-based debugging, right? Conforming to CustomDebugStringConvertible ensures the collection of one will print something reasonable based on its element.

The String(reflecting:) initializer will do its best to get a suitable string representation of _element: it’ll try debugDescription, regular description, and eventually fall back to showing the type name and member information.

extension CollectionOfOne : CustomReflectable {
  public var customMirror: Mirror {
    return Mirror(self, children: ["element": _element])
  }
}

Finally, a little reflection support. The important part of any collection type is its elements; that makes up the contents of what’s mirrored back.

The Closing Brace

There’s so much to see even in a simple collection of one.

Although collections are complicated things with many moving parts, protocols and extensions and protocol extensions make dealing with them much easier. You can study one aspect at a time before moving on to the next.

What I like about this type is it shows what you need to implement a simple collection type. What I like even better is what it doesn’t show — there are so many things handled for you in protocol extensions.

Leaning on standard library protocols lets you take advantage of proven patterns as well as integrate better into the language. Your own custom collection types could get properties such as count for free, and you could get iteration with for-in loops just like other collection types.

Speaking of iteration, there’s also an IteratorOverOne that goes with CollectionOfOne. Check out the follow-up post.

}