Learn Swift's range operators

Swift provides two new types of range operators that you’ll want to remember: the closed range operator (...) and the half-open range operator (..<):

The closed range operator is represented by three consecutive periods (...). Use this operator to specify a range that includes both its first and second endpoints. For example, consider this idiomatic loop in C:

for (int i = 1; i <= 10; i++) {
  printf("%d + %d is %d\n", i, i, i+i);
}

In Swift, this would be:

for i in 1...10 {
  print("\(i) + \(i) is \(i+i)")
}

// 1 + 1 is 2
// 2 + 2 is 4
// 3 + 3 is 6
// 4 + 4 is 8
// 5 + 5 is 10
// 6 + 6 is 12
// 7 + 7 is 14
// 8 + 8 is 16
// 9 + 9 is 18
// 10 + 10 is 20

You could also express this mathematically as the closed interval of integers that are greater than or equal to 1 and less than or equal to 10:

[1,10] = { x | 1 ≤ x ≤ 10 }

The half-open range operator is represented by two consecutive periods followed by a less-than symbol (..<). Use this operator to specify a range that includes its first endpoint but not its second, as is common when iterating over arrays and other containers with zero-based indices. For example, consider this idiomatic loop in C:

const int count = 10;

char* stars[count] = {
  "Alnilam", "Alnitak", "Bellatrix", "Betelgeuse", "Canopus",
  "Mintaka", "Polaris", "Rigel", "Saiph", "Sirius"
};

for (int i = 0; i < count; i++) {
 printf("%s\n", stars[i]);
}

In Swift, this would be:

var stars = ["Alnilam", "Alnitak", "Bellatrix", "Betelgeuse", "Canopus",
             "Mintaka", "Polaris", "Rigel", "Saiph", "Sirius"]
for index in 0..<stars.count {
  print(stars[index])
}

// Alnilam
// Alnitak
// Bellatrix
// Betelgeuse
// Canopus
// Mintaka
// Polaris
// Rigel
// Saiph
// Sirius

You could also express this mathematically as the left-closed, right-open interval of integers that are greater than or equal to 1 and less than 10:

[1,10) = { x | 1 ≤ x < 10 }

It’s important to note that both range operators expect the first endpoint to be less than the second endpoint. If you’d like to iterate over a range of numbers in reverse, use the reverse function:

print("Begin countdown:")

for i in reverse(1...10) {
  print(i)
}

// Begin countdown:
// 10
// 9
// 8
// 7
// 6
// 5
// 4
// 3
// 2
// 1

Avoid storing classes in structures

An alternate title for this article would be “Avoid storing reference types in value types,” because it’s very likely that their behavior will surprise you. To understand why, let’s first review the concepts of value and reference types in Swift:1

Types in Swift fall into one of two categories: first, “value types”, where each instance keeps a unique copy of its data, usually defined as a struct, enum, or tuple. The second, “reference types”, where instances share a single copy of the data, and the type is usually defined as a class.

But what does this mean in practice? Consider a simple structure with the coordinates of a point in 2D space:

struct PointStruct {
  var x: Int = 0
  var y: Int = 0
}

We can assign a PointStruct instance to a new variable (a) and set its x- and y-coordinates:

var a = PointStruct() // {x 50, y 75}
a.x = 50      // {x 50, y 0}
a.y = 75      // {x 50, y 75}

We can then assign our original PointStruct instance to another variable (b) and set the latter’s x- and y-coordinates, too:

var b = a     // {x 50, y 75}
b.x = 25      // {x 25, y 75}
b.y = 100     // {x 25, y 100}

As you can see below, each variable’s properties are modified independently because variable a was assigned to variable b by value:

a             // {x 50, y 75}
b             // {x 25, y 100}

Now let’s repeat this exercise with a class instead of a structure:

class PointClass {
  var x: Int = 0
  var y: Int = 0
}

var a = PointClass() // {x 0, y 0}
A.x = 50      // {x 50, y 0}
A.y = 75      // {x 50, y 75}

var B = A     // {x 50, y 75}
A.x = 25      // {x 25, y 75}
B.y = 100     // {x 25, y 100}

This time, each variable’s properties are not modified independently because variable A was assigned to variable B by reference:

A             // {x 25, y 100}
B             // {x 25, y 100}

This behavior is relatively straightforward, but the situation becomes a bit more. . .complicated when we start storing classes (reference types) in structures (value types).

Consider this simple structure representing a circle with an integer for the radius and a PointClass instance for the center:

struct Circle {
    var radius: Int = 1
    var center: PointClass = PointClass()
}

If we assign one circle (c) to another (d) and try to modify their centers independently, we may be surprised to find that they share the same PointClass instance!

var c = Circle() // {x 0 y 0}, radius 1
var d = c        // {x 0 y 0}, radius 1

c.center.x = 5   // {x 5 y 0}
c.center.y = 37  // {x 5 y 37}

c     // {x 5 y 37}, radius 1
d     // {x 5 y 37}, radius 1

Why? Because structures are assigned via shallow bitwise copies.

It gets worse.

You may recall that, by default, a value type’s instance methods cannot modify its properties.2 For example, Swift won’t let us add the following moveBy(x:y:) method to our 2D point structure:3

struct PointStruct {
    var x: Int = 0
    var y: Int = 0

    func moveBy(#x: Int, y: Int) {
        self.x += x // Error: Binary operator '+=' cannot be applied to two Int operands
        self.y += y // Error: Binary operator '+=' cannot be applied to two Int operands

    }
}

We must explicitly prefix our method with the mutating keyword to allow it to modify the structure’s properties:

struct PointStruct {
    var x: Int = 0
    var y: Int = 0

    mutating func moveBy(#x: Int, y: Int) {
        self.x += x
        self.y += y
    }
}

This is also true for instance methods that try to re-assign a class property, like this moveTo(_:) method in our circle structure:

struct Circle {
    var center: PointClass = PointClass()
    var radius: Int = 1

    func moveTo(point: PointClass) {
        center = point // Error: Cannot assign to 'center' in 'self'
    }
}

So far, so good: Swift won’t allow us to re-assign center in a non-mutating instance method. But what if we tried to edit center’s properties instead of assigning to center itself? Surely that’s against the rules!

struct Circle {
    var center: PointClass = PointClass()
    var radius: Int = 1

    func moveTo(point: PointClass) {
        center.x = point.x
        center.y = point.y
    }
}

Unfortunately, it isn’t. (Cue sad trombone.) Swift will happily let us muck around with a class’s properties—it just won’t let us re-assign the class reference itself.

Furthermore, Swift will allow us to call any method on a class from a structure’s non-mutating instance methods!

class PointClass {
    var x: Int = 0
    var y: Int = 0

    func moveTo(point: PointClass) {
        self.x = point.x
        self.y = point.y
    }
}

struct Circle {
    var center: PointClass = PointClass()
    var radius: Int = 1

    func moveTo(point: PointClass) {
        center.moveTo(point)
    }
}

ಠ_ಠ

It’s clear that Swift’s value semantics for structures only go so far. Namely, structures do not

  • make deep copies of any stored properties that are class instances
  • prevent instance methods from modifying the properties of class instances
  • prevent instance methods from calling mutating methods on class instances

This mix of value and reference semantics is potentially confusing and probably best avoided altogether. Refrain from nesting classes in structures; if you have a structure complicated enough to need a nested class instance, it may behoove you to declare it as a class, too.

  1. “Value and Reference Types.” Swift Blog - Apple Developer.

  2. “Methods.” The Swift Programming Language, Apple Inc.

  3. A function’s first parameter usually doesn’t have an external parameter name, but you can prefix it with a hash symbol to override this behavior. For more details, see The Swift Programming Language (Swift 2 Prerelease).