Overloading Custom Operators in Swift

In this Swift tutorial, you’ll learn how to create custom operators, overload existing operators and set operator precedence. By Owen L Brown.

Leave a rating/review
Download materials
Save for later
Share
Update note: Owen Brown updated this tutorial for Xcode 11, iOS 13 and Swift 5. Evan Dekhayser wrote the original.

Operators are the core building blocks of any programming language. Can you imagine programming without using + or =?

Operators are so fundamental that most languages bake them in as part of their compiler (or interpreter). The Swift compiler, on the other hand, doesn’t hard code most operators, but instead provides libraries a way to create their own. It leaves the work up to the Swift Standard Library to provide all of the common ones you’d expect. This difference is subtle but opens the door for tremendous customization potential.

Swift operators are particularly powerful because you can alter them to suit your needs in two ways: assigning new functionality to existing operators (known as operator overloading), and creating new custom operators.

Throughout this tutorial, you’ll use a simple Vector struct and build your own set of operators to help compose different vectors together.

Getting Started

Open Xcode and create a new playground by going to File ▶ New ▶ Playground. Pick the Blank template and name your playground CustomOperators. Delete all the default code so you can start with a blank slate.

Add the following code to your playground:

struct Vector {
  let x: Int
  let y: Int
  let z: Int
}

extension Vector: ExpressibleByArrayLiteral {
  init(arrayLiteral: Int...) {
    assert(arrayLiteral.count == 3, "Must initialize vector with 3 values.")
    self.x = arrayLiteral[0]
    self.y = arrayLiteral[1]
    self.z = arrayLiteral[2]
  }
}

extension Vector: CustomStringConvertible {
  var description: String {
    return "(\(x), \(y), \(z))"
  }
}

Here you define a new Vector type with three properties conforming to two protocols. The CustomStringConvertible protocol and the description computed property let you print a friendly String representation of the Vector.

At the bottom of your playground, add the following lines:

let vectorA: Vector = [1, 3, 2]
let vectorB = [-2, 5, 1] as Vector

You just created two Vectors with simple Arrays, and with no initializers! How did that happen?

The ExpressibleByArrayLiteral protocol provides a frictionless interface to initialize a Vector. The protocol requires a non-failable initializer with a variadic parameter: init(arrayLiteral: Int…).

The variadic parameter arrayLiteral lets you pass in an unlimited number of values separated by commas. For example, you can create a Vector such as Vector(arrayLiteral: 0) or Vector(arrayLiteral: 5, 4, 3).

The protocol takes convenience a step further and allows you to initialize with an array directly, as long as you define the type explicitly, which is what you’ve done for vectorA and vectorB.

The only caveat to this approach is that you have to accept arrays of any length. If you put this code into an app, keep in mind that it will crash if you pass in an array with a length other than exactly three. The assert at the top of the initializer will alert you in the console during development and internal testing if you ever try to initialize a Vector with less than or more than three values.

Vectors alone are nice, but it would be even better if you could do things with them. Just as you did in grade school, you’ll start your learning journey with addition.

Overloading the Addition Operator

A simple example of operator overloading is the addition operator. If you use it with two numbers, the following happens:

1 + 1 // 2

But if you use the same addition operator with strings, it has an entirely different behavior:

"1" + "1" // "11"

When + is used with two integers, it adds them arithmetically. But when it’s used with two strings, it concatenates them.

In order to overload an operator, you have to implement a function whose name is the operator symbol.

Note: You may define the overload function as a member of a type, which is what you’ll do in this tutorial. When doing so, it must be declared static so it’s accessible without an instance of the type that defines it.

Add the following piece of code at the end of your playground:

// MARK: - Operators
extension Vector {
  static func + (left: Vector, right: Vector) -> Vector {
    return [
      left.x + right.x,
      left.y + right.y,
      left.z + right.z
    ]
  }
}

This function takes two vectors as arguments and return their sum as a new vector. To add vectors, you simply need to add their individual components.

To test this function, add the following to the bottom of your playground:

vectorA + vectorB // (-1, 8, 3)

You can see the resultant vector in the right-hand sidebar in the playground.

Other Types of Operators

The addition operator is what is known as an infix operator, meaning that it is used between two different values. There are other types of operators as well:

  • infix: Used between two values, like the addition operator (e.g., 1 + 1)
  • prefix: Added before a value, like the negation operator (e.g., -3).
  • postfix: Added after a value, like the force-unwrap operator (e.g., mayBeNil!)
  • ternary: Two symbols inserted between three values. In Swift, user defined ternary operators are not supported and there is only one built-in ternary operator which you can read about in Apple’s documentation.

The next operator you’ll want to overload is the negation sign, which will change the sign of each component of the Vector. For example, if you apply it to vectorA, which is (1, 3, 2), it returns (-1, -3, -2).

Add this code below the previous static function, inside the extension:

static prefix func - (vector: Vector) -> Vector {
  return [-vector.x, -vector.y, -vector.z]
}

Operators are assumed to be infix, so if you want your operator to be a different type, you’ll need to specify the operator type in the function declaration. The negation operator is not infix, so you add the prefix modifier to the function declaration.

At the bottom of your playground, add the line:

-vectorA // (-1, -3, -2)

Check for the correct result in the sidebar.

Next is subtraction, which I will leave to you to implement yourself. When you finish, check to make sure your code is similar to mine. Hint: subtraction is the same thing as adding a negative.

Give it a shot, and if you need help, check the solution below!

[spoiler title=”Solution”]

static func - (left: Vector, right: Vector) -> Vector {
  return left + -right
}

[/spoiler]

Test your new operator out by adding this code to the bottom of your playground:

vectorA - vectorB // (3, -2, 1)
Owen L Brown

Contributors

Owen L Brown

Author

Evan Dekhayser

Author

Bas Broek

Tech Editor

Adriana Kutenko

Illustrator

Shai Mishali

Final Pass Editor

Richard Critz

Team Lead

Over 300 content creators. Join our team.