Unbounded Ranges in Swift 4

Toni Suter | August 21, 2017

In Swift, the expression 1...10 represents a closed range from 1 through 10. The ... is an infix operator that is declared in the standard library and the expression 1...10 applies the corresponding ... operator function to the operands 1 and 10. The value that results from this expression is an instance of the type CountableClosedRange<Int> which is also declared in the standard library. Additionally, Swift 4 adds one-sided ranges to the standard library as described in proposal SE-172.

However, recently I discovered that there is yet another kind of range in the Swift 4 standard library: the so-called unbounded range. Here’s how you can use unbounded ranges:

let str1 = "Example"
let str2 = str1[...]
print(str1, type(of: str1))    // test String
print(str2, type(of: str2))    // test Substring

let arr1 = [1, 2, 3]
let arr2 = arr1[...]
print(arr1, type(of: arr1))    // [1, 2, 3] Array<Int>
print(arr2, type(of: arr2))    // [1, 2, 3] ArraySlice<Int>

When you pass an unbounded range to the subscript of any collection, you get back a subsequence that contains all elements of the original collection. Apparently, this is a useful thing to do in a few rare cases, but I am going to ignore that here, because I am more interested in how this is implemented.

At first I thought that the Swift team must have added support for this in the Swift compiler, since it is not possible to declare an operator that doesn’t take any operands. But after a little bit of digging, I realized that this can be implemented with Swift’s existing language features and doesn’t require any changes to the compiler. Here’s how it works.

In contrast to the other ranges, the expression ... doesn’t actually call an operator function and doesn’t create an instance of some kind of range type. Instead it is just a reference to an operator function in the standard library. Here’s the declaration of that operator function (see Range.swift.gyb):

public enum UnboundedRange_ {
  public static postfix func ... (_: UnboundedRange_) -> () {
    fatalError("uncallable")
  }
}

As you can see, the operator function is declared as a static postfix operator function inside an enum type. However, those details don’t really matter, because the function is uncallable. This is because, the enum UnboundedRange_ is a so-called uninhabited type, which means that it is a type that doesn’t have any values. Therefore, we can’t create an instance of UnboundedRange_ which would be required in order to call the ... operator function. Instead, we can only reference the function. This function reference is then directly passed as an argument to the subscript. The following code shows how this subscript is implemented for types that adopt the _Indexable protocol (see Range.swift.gyb):

public typealias UnboundedRange = (UnboundedRange_)->()

extension _Indexable {
  // ...

  @_inlineable
  public subscript(x: UnboundedRange) -> SubSequence {
    return self[startIndex...]
  }
}

There is a typealias called UnboundedRange which makes it easier to declare methods and subscripts that take an unbounded range as an argument. In the example above, there is not much we can do with the parameter x inside the body of the subscript since x is an uncallable function and doesn’t have any properties or methods. Thus, the implementation just delegates to another subscript, passing a one-sided range that covers the entire collection.