What if arrays in Swift couldn’t be empty?

Hear me out: maybe Swift should have been designed with empty arrays being impossible. It’s madness, right? What other language has arrays that can’t be modeled as empty?

However, Swift already changes some of the rules from older languages like C. For example, there’s no break required in switch statements, opting for the more explicit fallthrough to combine cases instead. There’s no ++ operator, either. It’s confusing, duplicative, and ultimately the language is better without it.

Swift also differs from C by requiring explicit nullability. Swift lets you describe to the type system whether a single value is “empty” or not, using the Optional type. You can say whether you have a view controller, or something that might be a view controller, and might be .none. The type system can then check your work in all places, ensuring that the value is never missing when you expect it to be there.

Doubly Empty

When optionals and arrays interplay, however, you have two ways of expressing emptiness: nil and the empty array.

This can cause confusion, for example, when checking if an optional array is either nil or empty. For example, you might expect optional chaining to be effective, since it’s so useful in other parts of Swift. optionalArray?.isEmpty, however, returns an Optional<Bool>, which really distills down to the essence of a confused type. The type system will reject expressions that evaluate to optional bools in if statements.

optionalArray == [] will compile, but returns false for the case when the array is nil, which isn’t the expected behavior. You’re left with a few other choices that are all correct, but unwieldy:

if optionalArray == nil || optionalArray == [] {

if let array = optionalArray, array.isEmpty {

if (optionalArray ?? []).isEmpty {

if optionalArray?.isEmpty != false {

if optionalArray?.isEmpty ?? false {

The easiest way to get around this is to remember to never store optional arrays as properties. I’m pretty strict about this rule, making sure I don’t mix different types of emptiness. I also apply it other other types that can be “empty” as well — dictionaries, strings, booleans, sets, and sometimes numbers. Having to check two forms of emptiness is something I never want to do.

While its easy to follow this rule, for example, in the properties of a class, it’s not really possible to follow it in all cases. For example, getting an array property from an optional object will result in an optional array.

let wheels = optionalCar?.wheels // expression has type [Wheel]?

Getting a value out of a dictionary is the same.

let wheels = dictionary["wheels"] as? [Wheel]

You have to remember to tack on ?? [] to each of these expressions.

We just came from a world where you couldn’t separate the concept of a nullable view controller from a view controller to the type system. Gaining that ability simplified lots of expressions, reduced crashes, and generally clarified our code.

If the Array type couldn’t be empty, then optional arrays would represent empty arrays, and non-optional arrays would always have at least one item in them. One of the two forms of emptiness would be impossible, and any code that tried to use the other form would simply wouldn’t compile.

Modeling

Non-empty arrays are also useful for modeling. Sometimes it’s useful to express to the type system that a given array can never be empty. For example, perhaps your User class is modeled as having many email addresses, but shouldn’t be valid if the user has no email addresses. Expressing that to the type system would be awesome, but we currently can’t do that. Other examples:

  • A Country must have at least one City.
  • An Album must have at least one Song.
  • A Building can’t exist without at least one Floor.

The examples abound.

If the Array type couldn’t be empty, these relationships and their constraints would all be expressible to the type system, and you’d be prevented from removing the last item from arrays.

Expressions

With the advent of the Optional type, many expressions became simplified. When you know that a type can’t ever be null, you can skip certain checks and treat it in a more straightforward way. It’s the same with non-empty arrays. Currently, methods on the Collection protocol, like first, last, max and min return optionals, purely to handle the case that the array is empty.

There have been multiple cases where I knew an array was modeled such that it should never be empty, and when calling methods like first, I lamented the fact that I’d still have to guard that case even though I knew it was never possible and I just wanted to express that to the type system.

If the Array type couldn’t be empty, these methods could return non-optional values, and expressions that use them would be simplified. Empty arrays would use optional chaining to access these methods, and they would their results would be optional, just like today.

Appending

If arrays worked like this, appending to non-empty (now regular) arrays would work as normal. But appending to emptiable arrays would be a mess.

var emptiableArray = //...
emptiableArray == nil 
    ? emptiableArray = [newItem] 
    : emptiableArray?.append(newItem)

This is annoying, but the good news is that as of Swift 3.1, we can specialize specific types of generic extensions. That is, we can add a method to optionals that store concrete array types. (Before, you could only add extensions to specializations that used protocols.)

extension Optional<Array<Element>> {
    func append(_ element: Element) {
        switch self {
        case .some(array):
            array.append(element)
        case .none:
            self = [element]
        }
    }
}

And now we transparently have the behavior that we had before.

Without Loss Of Generality

To take this a step further, what if the type of an array baked in how long it was? For example, adding an element to an Array<of: 4, element: String> would return an Array<of: 5, element: String>. This concept is called dependent types and exists in some experimental languages with more advanced type systems, like Coq, Agda, and Idris. Oisín discusses how we can achieve a similar effect in Swift.

While these are super interesting, they’re also a little bit impractical. If you think about it, it means you can no longer store arrays in a class’s properties, unless you know that that the number of elements in the array won’t change. In a lot of cases, you also won’t know at compile time how many objects are going to come through an API or a database connection.

Simplying making the empty/non-empty distinction has a clear and practical use today, and it cleans up a lot of how Swift looks and works.

NonEmptyArray

This blog post is mostly a thought experiment. But it’s also a regular experiment. To that end, I built a non-empty array type. You can find it on GitHub here. It acts just like an array, but it isn’t emptiable. It conforms to Sequence, Collection, and has == and != methods.

Because of some part of the Swift type system that I don’t fully understand but appreciate nonetheless, you can override methods (like first) from a protocol (like Collection) but change its type from Element? to Element, and Swift will do the right thing at the call-site, and use the more precise type, Element. This means that NonEmptyArray correctly returns non-optional values for first, last, max and min, even though they’re defined on Collection as being Optional. There are tests in the repo asserting this.

Having an array that can’t be empty does interesting things. Methods that insert or append are fine, but methods that remove elements are more problematic. I marked the methods as throws, but after thinking about it more, this may not have been the right call. After all, removing elements from regular Swift arrays can also have deleterious effects, it’s just that they’re allowed to remove one more element than NonEmptyArray can. Swift arrays will fatalError when removing from an empty array, so perhaps that’s the right thing to do here as well.

I’m looking forward to breaking in the NonEmptyArray type in a few projects to see if the clean-up from having non-optional methods like first and friends is worth losing the type sugar that Swift’s built-in array type.