Neel Bakshi A guy who handles everything mobile.

Creating custom operators in Swift

7 min read 2183

Swift Logo

Table of Contents

Operators are one of the basic constructs of any programming language. They are represented as symbols and have various associated properties, and understanding them is crucial to mastering any programming language.

In this article, we’ll look at some of the operators that Swift ships with and also create our own operators.

What is an operator in Swift?

Before we begin looking at the different operators that Swift provides us with and creating custom operators, let’s understand a few basic terms related to them.
Here’s an example:

let a = 5
let b = 10
let sum = a + b

Here, we can see a few variables and constants being defined, along with a few symbols (e.g., = and +). Let’s define two terms here:

  • Operators: any symbol that is used to perform a logical, computational, or assignment operation. = is used to assign and + is used to add in the above example and are therefore operators
  • Operands: variables on which operations are performed, e.g., on line 1 we have a as an operand, and on line 3, we have a and b as two operands on which the + operator is being applied

Types of operators in Swift

There are three different types of operators, which are defined by the number of operands they work on.

  1. Unary operators: operators that work on only one operand (e.g., = and !)
  2. Binary operators: work on two operands, such as + , - , *, etc.
  3. Ternary operator: works on three operands, e.g., ?:

Operator notation

Notation defines the position of the operator when used with the operands. There are again three types:

  • Infix: when the operators are used in between operands. Binary and ternary operators are always infixed
  • Prefix: when the operators are used before an operand, e.g., ++, --, and !
  • Postfix: when the operators are used after an operand, e.g.,

Operator precedence and associativity in Swift

When working with operators in Swift, you should also know the priority order in which the operator is executed. Operator precedence only matters for infix operators, as they work on multiple operands.

For example, if an expression has multiple operators in it, Swift needs to know which operator needs to be executed first.

var result = 4 + 6 * 2 // 6*2 is done first which yields 12, then we do 4 + 12 = 16

In the above example, * has more precedence compared to +, which is why 6*2 is executed before 4+6.

In case you have two operators with the same precedence being used in an expression, they’ll fall back on their associativity. Associativity defines the direction in which the expression will start resolving in case the precedence of the operators are the same.

We made a custom demo for .
No really. Click here to check it out.

Associativity is of two types:

  1. left: this means that the expression to the left will be resolved first
  2. right: this means the expression to the right will be resolved first

For example, we have the following statement:

let result = 5 + 10 - 2 //13

Because both + and - have the same precedence, Swift falls back to the associativity of the operators. + and - have associativity of left, so we resolve the expression from the left so that + is evaluated first.

Here’s a table of the precedence order that Swift follows. The table lists the precedence in the order of decreasing priority.

Precedence Name Operators Included
BitwiseShiftPrecendence <<, >>, &<<, &>>
MultiplicationPrecedence *, /, %, &, &*
AdditionPrecedence +, -, |, ^, &+, &-
RangeFormationPrecedence ..<, ...
CastingPrecedence is, as, as?, as!
NilCoalescingPrecedence ??
ComparisonPrecedence <, <=, >, >=, ==, !=, ===, !==, ~=, .<, .<=, .>, .>=, .==, .!=
LogicalConjunctionPrecedence &&, .&
LogicalDisjunctionPrecedence ||, .|, .^
TernaryPrecedence ?:
AssignmentPrecedence =, *=, /=, %=, +=, -=, <<=, >>=, &=, |=, ^=, &*=, &+=, &-=, &<<=, &>>=, .&=, .|=, .^=

Now that we know the basics of what Swift operators are and how they are used, let’s look at popular operators that ship with Swift.

Common operators in Swift

The most common operators that Swift ships with fall under the following categories:

1. Assignment operator

Assignment operators are used to assign values to a constant or variable. The symbol is =.

2. Arithmetic operators

These operators are used to perform basic arithmetic operations, such as +, -, *, /, and %.

3. Logical operators

Logical operators are used to combine two conditions to give a single boolean value output, e.g., !, &&, and ||.

4. Comparison operators

These operators are used to compare numbers and give out a true or false output: >, <, ==, >=, <=, and !=.

5. Ternary operator

This operator is a shorthand operator for writing if-else conditions. Even though it does the same job, it is not a good idea to consider it a replacement for if-else conditions because it hampers readability.

Therefore, when you have different code blocks to run for different conditions, it’s better to use if-else blocks. For shorter, simpler cases, use the ternary operator. The operator, ?:, is used like this:

var value = 5 > 2 ? "5 is greater than 2" : "5 is less than 2"

6. Nil coalescing operator

This is a shorthand operator to return default values in case a particular variable is nil. The operator symbol is ?? and commonly used like this:

var identity: String? = nil
let nonNillableIdentity: String = identity ?? "Jane Doe" // since `identity` is nil, we return a default value of Jane Doe and store it in the nonNilalbleIdentity variable

7. Range operators

These operators are used to define ranges, e.g., and ..<. They are mainly used with array/strings to extract sub arrays/strings.

var array = [1,2,3,4,5]
let a = array[1...3] // [2,3,4]
let b = array[1..<3] // [2,3]
let c = array[2...] // [3,4,5]
let d = array[...3] // [1,2,3,4]

For a list of all the basic operators and their descriptions, read the Swift documentation.

Creating custom operators in Swift

Now that we’ve seen the operators that Swift ships with, let’s create our own operators. Custom operators are generally defined for three purposes:

  1. To define a completely new operator because you think the symbol’s meaning can express what the operator will do better than a function with a name can
  2. To define an operator that exists for basic data types like numbers or strings but does not exist for classes or structs that have been defined by you
  3. You want to override an operator that already exists for your classes and structs, but you need it to behave differently

Let’s look at all three purposes with examples.

Creating a new operator with a new symbol

Let’s say you want to define an operator with the symbol that calculates the hypotenuse of a right angle triangle given its two sides.

infix operator △
func △(lhs: Double, rhs: Double) -> Double {
    return sqrt(lhs*lhs + rhs*rhs)
}

var hypotenuse = 3△4 // 5

Creating an operator for a custom class or struct

We know that the + operator is used to add two numbers, but let’s say we wanted to add two instances of our struct called Velocity, which is a two-dimensional entity:

struct Velocity {
    var xVelocity: Double
    var yVelocity: Double
}

If we were to run the following command, you’d get a compiler error:

let velA = Velocity(xVelocity: 2, yVelocity: 4)
let velB = Velocity(xVelocity: 2, yVelocity: 4)
let velC = velA + velB // Binary operator + cannot be applied to two Velocity operands

In this case, it makes sense to define a + operator that takes care of adding two Velocity instances:

extension Velocity {
    static func +(lhs: Velocity, rhs: Velocity) -> Velocity {
            return Velocity(xVelocity: lhs.xVelocity + rhs.xVelocity,
                            yVelocity: lhs.yVelocity + rhs.yVelocity)
    }
}

Now the earlier statement will execute properly.

Overriding a Swift operator that already exists for a class/struct

Let’s say you have a struct that is used to define an item in a supermarket.

struct Item: Equatable {
    let id = UUID()
    let itemName: String
    let itemType: String
    let itemPrice: Double
}

When we need to compare two items, such that if two items have the same itemName, itemType, and itemPrice, we need to consider them equal regardless of what their id is.

If you run the following piece of code, you’ll see that the two variables are not equal:

let detergentA = Item(itemName: "Tide", itemType: "Detergent", itemPrice: 56.5)
let detergentB = Item(itemName: "Tide", itemType: "Detergent", itemPrice: 56.5)
let areDetergentsEqual = detergentA == detergentB // false

The reason for this is that instances detergentA and detergentB have different ids. In this case, we need to override the == operator and custom logic to determine equality.

extension Item {
    static func ==(lhs: Item, rhs: Item) -> Bool {
        return lhs.itemName == rhs.itemName &&
               lhs.itemPrice == rhs.itemPrice &&
               lhs.itemType == rhs.itemType
    }
}

Now that we know the different scenarios in which we define custom operators, let’s define our custom operator.

Defining a custom operator in Swift

A custom operator is defined just like a function is defined in Swift. There are two types of operators you can define:

  1. Global operator
  2. Operator specific to a class/struct

The definition looks like the following:

func <operator symbol>(parameters) { 
 // operator logic
}

There are different types of parameters you can define based on whether it is an infix or a prefix/postfix operator.

If it’s an infix operator, you can have two parameters, and, when it’s a prefix/postfix operator, you can have a single parameter.

Global operator

With global operators, we first define the operator with the notation keyword (infix , prefix or postfix) and the operator keyword, like this:

<notation> operator <operator symbol>

Here’s an example:

infix operator ^ // the operator is an infix operator with the symbol ^

Operator for class/struct

When we want to define an operator for a class or a struct, we need to define the operator function as a static function. The notation parameter is optional in case the operator is an infix operator (i.e., you have two parameters in the function signature), else you need to specify prefix or postfix:

extension <class name> {
  static <notation> func ^(parameters) { // note that this needs to be static function
    // operator logic
  }
}

Given that we’ve already looked at defining basic custom operators like + and ==, let’s review some examples of how you can define custom compound operators or operators that mutate the parameters themselves.

We’ll do this by defining a custom compound arithmetic operator for the Velocity struct from earlier:

extension Velocity {
    static func +=(lhs: inout Velocity, rhs: Velocity) { // note that the first parameter is an inout variable
        lhs.xVelocity = lhs.xVelocity + rhs.xVelocity
        lhs.yVelocity = lhs.yVelocity + rhs.yVelocity
    }
}

Now let’s execute the following statement:

var velA = Velocity(xVelocity: 5, yVelocity: 5)
var velB = Velocity(xVelocity: 5, yVelocity: 5)
velA += velB
print(velA) // Velocity(xvelocity: 10.0, yVelocity: 10.0)

Now, we’ll specify the notation of a custom operator.

Specifying the notation of a custom operator in Swift

In order to define the notation of a custom operator, you have three keywords for the corresponding notation:

  1. infix
  2. postfix
  3. prefix

When defining a global operator, you need to first define the operator’s notation, then define its functions. Note that this is not a compulsory step, but it’s necessary if you want to define global operators or hope to assign a precedence to it (which we’ll discuss in the next section).

Let’s look at the hypotenuse operator again, which is being defined as a global operator. You can see that we first define the operator with the notation and the symbol, then implement its logic:

infix operator △ // define the operator with its notation and symbol
func △(lhs: Double, rhs: Double) -> Double { // implement the operator logic
    return sqrt(lhs*lhs + rhs*rhs)
}

var hypotenuse = 3△4 // 5

When defining an operator for your own class/struct, you can directly use the notation keyword with the method definition. For example, let’s define a negation operator for Velocity as well, which negates both the xVelocity and yVelocity properties. This operator is going to be defined as a prefix operator:

extension Velocity {
    // defining a prefix negation operator
    static prefix func -(operand: Velocity) -> Velocity {
        return Velocity(xVelocity: -val.xVelocity, yVelocity: -val.yVelocity)
    }
}

let vel = Velocity(xVelocity: 4, yVelocity: 4)
print(-vel) // xVelocity: -4, yVelocity: -4

Setting precedence of custom Swift operators

You can also define the precedence of your operator using the precedence keywords, as mentioned in the precedence table above. This is usually done in the definition step:

infix operator ~: AdditionPrecedence // now this operator's priority is the same at the operators mentioned in the AdditionPrecedence
func ~(lhs: Int, rhs: Int) -> Double {
  return sqrt(Double(lhs*lhs-rhs*rhs))
}

This way, the compiler knows which operator needs to be executed first. In case no precedence is specified, it defaults to DefaultPrecedence, which is higher than TernaryPrecedence.

Conclusion

This brings us to the conclusion of what operators are in Swift and how you can create your own. Although they are pretty simple to use, understanding them is important because they are one of the most foundational constructs of any programming language.

You should be familiar with basic operators, as you will be using them frequently in your code, and, when it comes to custom operators, define them only if the symbol’s original meaning makes sense for you.

: Full visibility into your web apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

.
Neel Bakshi A guy who handles everything mobile.

Leave a Reply