The Ultimate Guide to Operators in Swift
Operators in Swift are tiny symbols that produce a result. You’ve got comparison operators, logical operators, and operators like +
and -
for simple math. In this tutorial, we’re diving deep into Swift’s operators, including precedence and associativity, and even write our own custom operators.
Here’s what we’ll discuss:
- The most important operators: math, assignment, comparison, logic, ranges
- How you can code your own custom operators
- Important but complex topics: precedence, associativity and grouping
- Pretty much every operator under the sun: from
!a
to??
Ready? Let’s go.
- Operators in Swift
- Math Operators
- Assignment Operators
- Comparison Operators
- Logical Operators
- Range Operators
- Really Cool Operators
- Precedence, Associativity and Grouping
- Custom Operators
- Further Reading
Operators in Swift
What’s an operator? Let’s start with an example. Check this out:
let result = 3 + 4
print(result)
// Output: 7
In the above Swift code, the +
is an operator. You know it as the “plus” symbol, which is used to add one number to another and return the result.
In Swift, and in programming in general, we’ve got lots and lots of operators. This tutorial will show you how to use them, and “rules” to keep in mind, like precedence and associativity.
Before we continue, we’ll have to discuss some terminology:
- Operator: Function that produces a result based on 1, 2 or 3 input values. We’ve got arithmetic, logical, comparison, range operators – the works!
-
Operand: The input for an operator, i.e. the numbers left and right of the
+
sign. You can also call them targets or parameters. - Expression: A combination of operators and operands, including function calls, variables, types, etc., which represents a (return) value.
-
Result: The value that’s returned by an expression, i.e.
7
is returned by the expression3 + 4
.
In this tutorial, you’ll often see the placeholders or variables a
, b
and c
. They usually refer to the first, second and third operands (inputs) of a function. We’ll sometimes say !isLoggedIn
, but it may be clearer to write !a
when discussing how the logical NOT operator !
works.
In general, we can separate operators in Swift into 4 groups:
-
Math: These operators involve simple arithmetic, like addition
+
, multiplication=
and remainder%
. -
Comparison: These operators return a boolean value, like
a > b
. You use them to compare numbers and strings. -
Logical: These operators are used to evaluate and combine boolean values, such as the logical NOT operator
!a
. -
Advanced: These operators provide a bit of syntactical sugar around common operations, such as the nil-coalescing operator
a ?? b
.
In programming we also distinguish between unary, binary and ternary operators. They refer to the number of inputs (operands) an operator has. This is important for remembering syntax, but also because it tells you something about what an operator does.
-
Unary: A unary operator has 1 operand (input). An example is
!a
or!isLoggedIn
, for the logical NOT operator!
to invert a boolean value. -
Binary: A binary operator has 2 operands (inputs). An example is
a > b
or5 > 6
, for the greater-than comparison operator>
. - Ternary: A ternary operator has 3 operands (inputs). Swift only has 1 ternary operator, the ternary conditional operator (discussed below).
The last bit of terminology revolves around the position of the operator:
-
Prefix: A prefix operator is written before the operand, like
!
in!a
(logical NOT). -
Infix: An infix operator is written in between 2 operands, like
+
ina + b
. -
Postfix: A postfix operator is written after the operand, like
!
ina!
(force unwrapping).
Quick Tip: Unary has “uno” for 1, binary has “bi” for 2, and ternary has “tri” for 3. Each of these terms comes from Latin (unus, binarius, ternarius).
Math Operators
Let’s start with the simplest operators of the bunch: mathematics. They work exactly as you’d expect in code, as they do in algebra class.
Arithmetic
In Swift, we’ve got the 4 standard arithmetic operators +
(plus), -
(minus), *
(multiplication) and /
(division). Each of these operators are binary, so you’ll add one number to another with a + b
for example. They (mostly) work with numbers, like integers and doubles.
3 + 4 // 7
7 - 9 // -2
4 * 4 // 16
9 / 3 // 3
You can also use +
to combine 2 strings into one. This is called string concatenation. Like this:
let line = "So long" + " and thanks for all the fish!"
// Output: So long and thanks for all the fish!
Speaking of strings and integers – because Swift is a strongly typed language, you can only use operators on 2 operands that have the same type. That means – strictly speaking – you can only add 2 integers, or 2 strings, or 2 doubles to each other.
There are plenty of exceptions, though. If you try to add an integer to a double, for example:
let value = 1 + 2.0
print(value)
// Output: 3.0
In the above code, Swift uses type inference to figure out that the type of value
must be Double
because one of its operands is Double
.
Behind the scenes, Swift will convert 1
to 1.0
. This makes programming considerably easier, and easier to read, because you don’t have to manually type cast that integer to a double.
Plus Minus Sign Operators
You can use the unary minus operator -a
to toggle (or “flip”) the sign of a number, i.e. from positive to negative and vice versa. You can also use the unary plus operator +a
, but that won’t do anything!
Let’s take a look at an example:
let positiveNumber = 42
let negativeNumber = -positiveNumber
let whatNumber = -negativeNumber
print(positiveNumber, negativeNumber, whatNumber)
// Output: 42 -42 42
As you can see in the above code, the first number is positive, and then the -positiveNumber
flips the sign to negative, and a second time back to positive, with -negativeNumber
.
When we change the 3rd line to this …
let whatNumber = +negativeNumber
… the value of whatNumber
is still -42
. It’ll stay unchanged from negativeNumber
because the +a
operator doesn’t do anything!
You may find that, in day-to-day coding, the unary minus operator is impractical and hard to read. In those scenarios where you want to make explicitly clear that the sign of an expression has changed, you can use the operation a * -1
. Like this:
let value = (3 + 4 / (2 * 3 * 4) - 4 / (4 * 5 * 6) + 4 / (6 * 7 * 8)) * -1
print(value)
// Output: -3.145238095
The expression (···) * -1
is often clearer than -(···)
. That depends on your use case, of course, but it’s good to know these little math tricks exist.
Remainder: isMultiple(of:)
The last math operator we’ll discuss is the remainder operator a % b
. It’ll return the remainder of a division, i.e. what number remains if you fit a
in b
as many times as possible.
An example:
let value = 7 % 2
print(value)
// Output: 1
The above expression 7 % 2
returns 1
because you can fit 3 times 2 in 7, which leaves 1. It’s the basic “John has 7 apples and 3 friends, how many apples are left if he gives each friend 2 apples?”
You often use the remainder operator a % b
to calculate if a given number is a multiple of another number, or is divisible by that number. The most common scenario is, of course, to calculate if a given number is even or odd.
Check this out:
for i in 0..<10 {
let value = i % 2 == 0 ? "\(i) is even" : "\(i) is odd"
print(value)
}
// Output: 0 is even, 1 is odd, 2 is even, 3 is odd ···
Instead of the remainder operator, you can use the function isMultiple(of:)
. Like this:
let value = 144
let result = value.isMultiple(of: 12)
print(result)
// Output: true
Awesome! Let’s continue with assignment operators.
Assignment Operators
You use the assignment operator a = b
in Swift to assign a value b
to a value a
. Differently said, you use the assignment operator to assign a value to a variable, constant or property.
Check this out:
let age = 42
print(age)
// Output: 42
In the above code, we’ve assigned the integer 42
to the constant age
. What’s on the right side of the =
symbol is assigned to what’s on the left side of it.
That sounds so simple, but there’s more! For example, the assignment operator does not return a value. The expression age = 42
does not have a result, unlike, for example, the expression a + 4
.
It’s easy to mistake the =
operator with the equals operator ==
, which is why Swift won’t return a value for age = 42
, which helps you avoid coding errors, for example with making a typo in if age = 42 { ··· }
.
Compound Assignment Operator
Although the =
assignment operator is technically the only one in Swift to assign a value to a variable, we’ve also got 4 compound assignment operators. In short, they perform an operation, like addition, and also assign the result to a variable.
Here’s an example:
var i = 3
i += 1
print(i)
// Output: 4
The a += b
adds b
to a
and assigns the result to a
. It’s increasing the value of a
by b
. The a += b
syntax is identical to a = a + b
.
The above code also shows how Swift is different from other programming languages. C-style languages, like Java, have the a++
and a--
operators. They increase or decrease a
by one. Swift doesn’t have those, it just has a += 1
and a -= 1
. (Try it, ++
really won’t work!)
In total, we’ve got 10 compound assignment operators, of which 4 are the simplest and most common. They are:
-
a += b
: add and assign -
a -= b
: subtract and assign -
a *= b
: multiply and assign -
a /= b
: divide and assign
The other compound assignment operators involve bit shifting and bitwise operators. They are <<=
, >>=
, &=
(AND), |=
(OR), and ^=
(XOR).
It’s easy to see how +=
and -=
work, but what about /=
and *=
? Let’s take a look at an example:
func power(base: Int, exponent: Int) -> Int
{
var result = 1
for _ in 0..<exponent {
result *= base
}
return result
}
let value = power(base: 2, exponent: 4)
print("2 to the power 4 is \(value)")
// Output: 2 to the power 4 is 16
In the above function, we’re calculating the power (exponent) of a number, or result = baseexp. We do this by multiplying result
and base
a number of times in a for loop.
In the function power()
, we’re using the compound assignment operator *=
to multiply result
with base
and assign the value back to result
. Neat!
Comparison Operators
Alright, let’s move on to comparison operators. You use comparison operators in Swift to compare things with each other. A comparison operator will always return a boolean, i.e. true
or false
, of type Bool
.
We’ve got 6 comparison operators in Swift:
-
a == b
: Equal to -
a != b
: Not equal to -
a > b
: Greater than -
a < b
: Less than -
a >= b
: Greater than or equal to -
a <= b
: Less than or equal to
If you look closely, you’ll see that comparison operators have 3 groups of sorts: equal or not, greater/less than, and greater/less than or equal. Especially the >=
and <=
operators will come in handy.
Identity Operators
Swift also has 2 identity operators, ===
and !==
(“triple bar”). You use them to check if 2 values are identical to each other. Note the difference between equality and identicality:
- Equality: Values are said to be equal when their representations are the same, according to the type you’re using.
- Identicality: Objects are said to be identical when they refer to the exact same object.
You may find that 2 pairs shoes of the same type are equal because they have the same brand, type and color, but they aren’t identical because they’re worn by 2 different persons. Your shoes aren’t their shoes (unless you share them, which would be mildly disturbing).
Equal and Not Equal
You use a == b
and a != b
to check if 2 values are equal to or not equal to each other. Both of these expressions return a value that’s either true
or false
, which means you’ll typically use the equality operators – and other comparison operators – together with if statements.
Let’s take a look at an example:
for i in 0..<5 {
if i == 3 {
print("i is 3")
} else {
print("i is not 3")
}
}
The output of the above code is:
i is not 3
i is not 3
i is not 3
i is 3
i is not 3
In the above code, we’re using the ==
operator to test if i
is equal to 3
. If it is, we’ll print out a bit of text. With the else
clause in the conditional, we’re taking action when i == 3
evaluates to false
.
Because comparison operators like ==
return either true
or false
, we can invert the above conditional. Like this:
if i != 3 {
print("i is not 3")
} else {
print("i is 3")
}
The result of both pieces is the same, because the ==
operator is the opposite of !=
. When the expression a == b
returns false
, the expression a != b
returns true
. That’s because true
and false
are opposites. Swift is logical like that!
Quick Note: If you want to learn more about logic, conditionals and how to use them, check out this tutorial: Conditionals in Swift with If, Else If, Else
Greater Than, Less Than, Or Equal
Next up, let’s talk about the other 4 comparison operators. They are:
-
a > b
: Greater than -
a < b
: Less than -
a >= b
: Greater than or equal to -
a <= b
: Less than or equal to
You can use these operators on numbers or on strings. They let you compare things with each other. For example, if one number is greater than the other. Just like ==
and !=
, the above operators will always return either true
or false
.
Of course, 5 > 4
will always return true
. In programming you often work with user input, or dynamic values from some data source, which means you’ll want to test that variable against a fixed number. That’s where comparison operators can help.
Check out the following example:
let numbers = (0..<10).map { _ in Int.random(in: -10...10) }
for number in numbers {
if number > 0 {
print("\(number) is above zero")
}
}
// Output: 4 is above zero, 1 is above zero, 10 is above zero
In the above code, we’re using the >
operator to check if number
is greater than zero. If the expression number > 0
returns true
, we’ll print a bit of text. The first line of code generates an array of 10 random numbers between -10 and +10.
Let’s take a look at another example:
let likes = 3
if likes == 0 {
print("0 likes")
} else if likes == 1 {
print("1 like")
} else if likes >= 2 {
print("\(likes) likes")
}
// Output: 3 likes
In the above example, we’re using the equal to ==
and greater than or equal to >=
operators to compare the value of the likes
constant. Based on its value, we print a plural/singular line of text: 0 likes, 1 like, 3 likes.
As you’ve guessed, the a >= b
operator returns true
if a
is greater than b
or if a
is equal to b
. It’s ==
and >
rolled into one. But why do you need that >=
operator, if you’ve got ==
and >
?
In programming, there’s always more ways than one to solve a problem. Your job as a coder isn’t necessarily to solve the problem, but to find the approach that’s easiest to implement, or the most bug-free, or the easiest to maintain and extend in the long run. You’ve got many tools in your toolkit to get there!
Comparison operators will often revolve around the bounds of things. For example, we can assert that the last index number of an array in Swift is equal to the size of that array minus one.
The first index outside the bounds of that same array is also equal to or greater than the number of items in the array. The operators >=
and <=
help you with correctly expressing those bounds in code, without needing to + 1
or - 1
everywhere.
Comparing Strings
Before we move on, one last thing: you can also compare strings with each other in Swift. If you give each letter from A to Z a number, you’ll see that you can compare text just as you would numbers.
Here’s an example:
let names = ["Ford", "Arthur", "Zaphod", "Trillian"]
print(names.sorted(by: <))
// Output: ["Arthur", "Ford", "Trillian", "Zaphod"]
In the above code, we’re using the less than operator <
to sort the array with names alphabetically in ascending order (A-Z). The sorted(by:)
function will compare each of the strings in names
against each other, and come up with a definitive sort order.
let value = "Ford" > "Arthur"
print(value)
// Output: true
For example, the name Ford is greater than Arthur because the F comes after the A in the alphabet.
Logical Operators
So far, we’ve looked at mathematical operators like +
and -
, at (compound) assignment operators like =
and +=
, and at comparison operators like !=
and >
. The fact that comparison operators, like a > b
, produce boolean values is the perfect segway into the next topic: logical operators.
With logical operators you can transform boolean values. They produce a boolean value based on some logical rule. This allows you to create complex logical expressions. You can use these to take decisions in your code.
Swift has 3 common logical operators:
-
a && b
: Logical AND -
a || b
: Logical OR -
!a
: Logical NOT
Logical operators take boolean values as inputs, and produce a boolean value as output. You’ll always use &&
, ||
and !
together with true
and false
. As you’ve guessed, the inputs for logical operators often come from the results of comparison operators.
Let’s take a look at an example:
if threatLevel > 5 && officerRank >= 3 || isPresident {
print("!!! STRATEGIC CANDYBAR RESERVE DOORS OPENING !!!")
} else {
print("no candy for you")
}
In the above if
statement, we’ve got an expression that uses many operators, including &&
and ||
. Let’s break that down:
-
threatLevel > 5 && officerRank >= 3 ···
(AND) -
··· || isPresident
(OR)
We can assert that the doors to the Strategic Candybars Reserve will open if:
- The threat level is greater than 5 AND your officer’s rank is greater or equal to 3
- OR you’re the president
Double-check this with me: If you’re the president, the doors will open anyway. And if your officer rank is equal or over 3, and the threat level is sufficiently high – the doors open as well. We can assert that a high threat level and officer rank merits the same “authority” as the president.
Based on what we’ve discussed so far, you see how the &&
(AND) and ||
(OR) operators will produce a boolean result based on some rules. In short, we can summarize those rules like this:
-
a && b
(AND) returnstrue
if botha
andb
aretrue
-
a || b
(OR) returnstrue
if eithera
orb
istrue
You can also put those rules in a truth table. Like this:
a |
b |
AND | OR |
---|---|---|---|
true | true | true | true |
true | false | false | true |
false | true | false | true |
false | false | false | false |
See what’s happening? The OR operator will return true
if either operand is true
. The AND operator will only do that if they’re both true
. Neat!
This leaves the !a
NOT operator. How it works is simple: it’ll invert the value a
. So when a
is true
, !a
is false
. And when a
is false
, !a
is true
.
Here’s a lovely truth table for the NOT operator:
a |
NOT |
---|---|
true | false |
false | true |
Consequently, !!true
– a valid expression – is… not not true, which is not false, which is true. Intriguing, right? You wouldn’t use that in everyday programming, but it goes to show that the logical statements in your code are solid, cast in stone. It’s either true
or false
– no doubt about it.
This also means you can often invert or switch logical operations to make them easier to comprehend. A logical expression says as much about what it confirms, as what it denies. Check this out:
func openDoorsIfAllowed()
{
if threatLevel <= 5 && officerRank < 3 && !isPresident {
return
}
toggleDoors(.open)
}
See what’s going on in the above code? Take a minute to compare it to the previous conditional.
We’re playing around with code here. The outcome of the above conditional looks almost the same as the previous one. In this function, it makes sense to exit the function when the conditions for opening the doors aren’t valid.
If isPresident
is true
, that’ll cause the condition to fail, which opens the doors. If threatLevel
is greater than 5 or officerRank
is greater than or equal to 3 or isPresident
is true
, the doors open as well.
Wait a minute… You see 2 &&
operators, right? Then why are we talking about OR there? That’s because the &&
operator returns false
if either of the operands is false
. As it turns out, both conditionals aren’t exactly the same – but they look a lot alike!
Quick Note: When in doubt, break up your logical expression into multiple if
statements. Coding 2 separate if
blocks is the same as “OR” (if this, or if that), and 2 nested if
blocks is the same as “AND” (if this and then if that).
Range Operators
The Swift Programming Language also provides operators that make your life as a coder easier, such as range operators. They don’t involve math or logic. Instead, you can use them to concisely code something that normally would have taken more code to get to the same result. This is often referred to as “syntactic sugar”.
You use range operators in Swift to denote a range between values, like from zero to 100. You can also use ranges with indices in collections, which is quite handy.
Swift has 2 range operators, in 2 groups:
-
a ... b
: The closed range operator, so a range froma
tob
includingb
-
a ..< b
: The half-open range operator, so a range froma
tob
but not includingb
You can use both the ...
and ..<
operators for full ranges, and for one-sided ranges. Let’s take a look at a few examples, to understand how that works.
First, the closed range operator:
let numbers = Array(1...10)
print(numbers)
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Then, the half-open range operator:
let numbers = Array(1..<10)
print(numbers)
// Output: [1, 2, 3, 4, 5, 6, 7, 8, 9]
See how the first example includes numbers including 10, and the second does not? That’s the difference between ...
and ..<
. The half-open range operator is especially useful when dealing with arrays. Check this out:
let names = ["Arthur", "Ford", "Zaphod", "Trillian", "Marvin"]
for i in 0..<names.count {
print(names[i])
}
// Output: Arthur, Ford, Zaphod, ···
See how we’re using the ..<
operator to make a range of integer numbers from zero up to but not including names.count
? That’s because the size of an array is equal to its last index number. If we would have used ...
, we would have run into that index out of bounds problem.
You can also use the ...
and ..<
operators to get a slice of an array, i.e. part of the array as defined by the bounds of the range. Check this out:
print(names[0..<names.count]) // Full range
// ["Arthur", "Ford", "Zaphod", "Trillian", "Marvin"]
print(names[0...]) // One-sided, full range
// ["Arthur", "Ford", "Zaphod", "Trillian", "Marvin"]
print(names[2...]) // One-sided, index 2 up to end
// ["Zaphod", "Trillian", "Marvin"]
print(names[...2]) // One-sided, beginning up to index 2
// ["Arthur", "Ford", "Zaphod"]
print(names[..<2]) // One-sided, beginning up to but not incl. 2
// ["Arthur", "Ford"]
print(names[2..<4]) // Full range, 2 to 4 (not including)
// ["Zaphod", "Trillian"]
Quick Note: In the above code, 2
refers to the third item in the array. This item has integer index number 2
. Arrays indices in Swift start at zero!
Really Cool Operators
Swift has 2 really cool operators: the nil-coalescing operator and the ternary conditional operator.
Nil-coalescing Operator
Check this out:
let author:String? = nil
let label = author ?? "Unknown author"
print(label)
// Output: Unknown author
In the above code, we’re using the nil-coalescing operator a ?? b
to provide a default value if author
is nil
. The nil-coalescing operator will return a
if a
is not nil
, and if a
is nil
, it’ll return b
. The ??
operator also unwraps the optional.
Like this:
let author:String? = "J.K. Rowling"
let label = author ?? "Unknown author"
print(label)
// Output: J.K. Rowling
This is the exact same code, except now the optional author
has a string value instead of nil
. Unlike before, the value of author
is assigned to label
, which subsequently prints the string "J.K. Rowling"
.
The code we’ve used so far is identical to the code below, which goes to show that the a ?? b
operator saves you from writing a whole lotta code.
let author:String? = nil
var label = ""
if author != nil {
label = author!
} else {
label = "Unknown author"
}
print(label)
Ternary Conditional Operator
The only operator in Swift that has 3 operands is the ternary conditional operator. Let’s look at an example:
let temp = 25 // °C
let value = temp > 18 ? "it's hot" : "it's cold"
print(value)
// Output: it's hot
In the above code we’re using the a ? b : c
operator to choose between b
and c
based on the expression a
. When a
is true
, b
is returned, and when a
is false
, c
is returned. It’s a short-hand for a conditional.
The above code is exactly the same as this:
let temp = 25 // °C
var value = ""
if temp > 18 {
value = "it's hot"
} else {
value = "it's cold"
}
// it's hot
Just as the nil-coalescing operator, the ternary conditional is much more concise. This usually – not always – makes your code easier to read and comprehend.
Did you know you can implement the ??
operator with ?:
, and vice versa? It’s a bit silly, of course – but nevertheless, you can, because they’re both syntactic sugar around more verbose bits of code. Check this out!
1. a ? b : c
with a ?? b
:
let temp = 25 // °C
let value = {
if temp > 18 { return "it's hot" } else { return nil }
}() ?? "it's cold"
print(value)
// Output: it's hot
2: a ?? b
with a ? b : c
:
let author:String? = nil
let label = author != nil ? author! : "Unknown author"
print(label)
// Output: Unknown author
Precedence, Associativity and Grouping
Before we call it quits, we’ll have to discuss 3 important but often overlooked concepts for working with operators in Swift. They are:
- Precedence: When using multiple operators together, which operations are done before the others?
- Associativity: When 2 operators have the same precedence, do the operands on the left or the right belong to the operator?
-
Grouping: Changing the precedence of operators by grouping operators together within parentheses
( )
or emphasizing their existing order by adding parentheses( )
(without changing their order).
Precedence
First, let’s talk about precedence. It sounds more complex than it actually is! Check out the following calculation:
let a = 2 + 3 * 4
print(a)
// ???
What’s the value of a
in the above code?
20
14
*tick* *tock* *tick* *tock*
It’s 14! Because multiplication goes before addition, so the result 3 * 4
is calculated first (12) to which 2
is added (14). The other way around – which is incorrect – would be 2 + 3 = 5
times 4
is 20
.
This is a simple example, but it gets more complicated if you consider all operators that Swift has. Precedence, i.e. the order of operators, not only affects arithmetic, but also extends to comparison- and logical operators.
Let’s discuss a few ways of looking at this. In practical, day-to-day Swift programming, you’ll need to remember this:
- Precedence rules follow the rules you learned in math:
* / + -
- Math operators go before comparison operators go before logical operators
- Logic operator rules:
&&
(AND) goes before||
(OR) - Prefix operators
!a
,+a
and-a
go first
That’s it! This should get you pretty far. When in doubt, you can always look up the documentation on precedence rules in Swift.
But what’s the real explanation here, beyond shorthands? In reality, precedence rules are divided into groups. The precedence rule for the group determines what, for example, the order of logical operator &&
is in combination with >=
and +
.
In short, the first-to-last order is:
-
MultiplicationPrecedence
for* / %
-
AdditionPrecedence
for+ -
-
RangeFormationPrecedence
for ranges -
CastingPrecedence
for type casting operators -
NilCoalescingPrecedence
for??
-
ComparisonPrecedence
for all comparison operators -
LogicalConjunctionPrecedence
for&& ||
-
TernaryPrecedence
for?:
-
AssignmentPrecedence
for all assignment operators
Awesome!
Associativity
In the above list for precedence you can see that multiple operators are grouped together. How do you determine the order of operators in a group, when they have the same importance? That’s where associativity comes in.
You’ve got 3 types of associativity:
- Left-associative: Grouped from the left, so left-most operator “grabs” left-most operands first
- Right-associative: Grouped from the right, so right-most operator “grabs” the operands first
- Non-associative: Operators cannot be grouped (most notably, comparison operators b/c their input type is different from their output type)
Here’s a simple example:
let a = 7 - 4 + 2
print(a)
// Output: ?
We’ve seen in the list for precedence that both the a + b
and a - b
operators have the same precedence group. When you combine them, which operation goes first?
-
(7 - 4) + 2 = 5
(left-associative) -
7 - (4 + 2) = 1
(right-associative)
Arithmetic operators like + - * /
are left-associative. This means that, when they’re used together, we can “grab” the left-most operation together. Kinda lika a cowboy throwing a lasso/rope around them. Like this:
let a = (7 - 4) + 2
print(a)
// Output: 5
The operation 7 - 4 = 3
goes first, after which 2
is added, resulting in 5
. Because -
and +
are left-associative, the left-most operator groups its operands (7
and 4
), which goes first, and then +
goes second. This results in a definitive order of operations!
In short, associativity rules for operators in Swift are as follows:
-
* / % + -
are left-associative -
??
and?:
are right-associative -
&&
and||
are left-associative -
=
and assignment operators are right-associative
What about groups that have no associativity? In Swift, all comparison operators are non-associative. They do not have associativity rules between them. How is that even possible!?
It’s simpler than it looks. Because comparison operators take numbers (and strings) as input, but produce booleans, you’d end up with a type conflict if they were left- or right-associative. It’s only logical to either avoid that conflict, or to throw an error because the expression is invalid.
let a = 5 > c < 4
// Invalid expression – this wouldn't make sense!
On top of that, you only combine comparison operators with logical operators (if at all). Logical operators are left-associative, which means you can use their associativity to prevent a conflict between multiple comparisons.
let a = followerCount >= 0 && userLevel > 3 || !isLoggedIn
// What's the order? Comparison first, then &&, then ||
Who grabs the operand userLevel > 3
in the above code? It’s the &&
, because AND goes before OR. There is no conflict among the comparison operators, because they aren’t combined and belong to their own group.
Grouping
You can create groups in your expressions with parentheses ( )
. This enables you to change the order of operations, because the expressions within the group are evaluated before the ones outside of it.
You can do this for 2 reasons:
- Improve the readability of your code, to make implicit precedence rules clearer
- Change the order of operations for expressions that would otherwise return an incorrect result
Let’s go back to that previous conundrum with +
and *
. This one:
let a = 2 + 3 * 4
// Output: 14
What if you actually needed 20
here, instead of 14
? That’s when you can use grouping with parentheses to get to the right result. Like this:
let b = (c + d) * e
In the above code, the expression c + d
is calculated before multiplying its result with e
. If you wouldn’t have used the parentheses, the calculation d * e
would have gone first.
The other reason to use grouping with parentheses is to make an expression clearer. We’ve discussed precedence and associativity rules, but they aren’t always as easy to remember. On top of that, most conditional expressions involve complex variable names that can impair readability.
You can help yourself and other developers by grouping expressions together, according to their pre-existing precedence rules, to emphasize the order of operations. Take these 2 expressions, for example:
if threatLevel > 5 && officerRank >= 3 || isPresident { ···
if (threatLevel > 5 && officerRank >= 3) || isPresident { ···
The results of both conditionals are exactly the same. We know that &&
goes before ||
, and the officerRank >= 3
isn’t grabbed by the ||
operator. Instead, they’re left-associative, which means it’s an operand for the &&
operator and not ||
.
We’re emphasizing this order with the parentheses. At a glance, we can see that the code inside the parentheses is evaluated first. Its result is then evaluated with ··· || isPresident
.
You’ll want to use this sparingly, of course, and not as an excuse to avoid learning more about precedence rules and associativity. If you add parentheses for all precedence rules involved in your expression, you lose the effect of hinting at what goes first.
Custom Operators
Last but not least: custom operators. You can build your own operators in Swift! Let’s take a look at an example:
infix operator ^^
func ^^ (base: Int, exponent: Int) -> Int {
var result = 1
for _ in 0..<exponent {
result *= base
}
return result
}
let value = 2 ^^ 4
print(value)
// Output: 16
In the above code, we’ve created an infix operator a ^^ b
that calculates the power of a number. Apart from the first line of code, this custom operator looks exactly like an ordinary Swift function.
You’ll need 2 things to create a custom operator in Swift:
- Operator declaration, like
infix operator ^^
- Implementation of the operator with a function
In the above code, on the first line, we’re creating a custom operator ^^
. It’s an infix operator, so it’ll go between 2 operands. We could also have chosen postfix
or prefix
. The declaration also includes the word operator
.
Then, we’re declaring a function with the “name” of the operator. We’re giving this function 2 parameters, one on each side of the operator. You’ll often see these parameters as lhs
and rhs
, meaning left-hand side and right-hand side. For our ^^
operator, it makes more sense to call them base
and exponent
.
In the last part of the code, we’re putting the a ^^ b
operator to good use. 2 to the 4th power is 16!
You’ll notice that the ^^
operator, as implemented above, is limited to the integer Int
type. You can only use it with integers; use it with anything else and you’ll get a type error.
The power of custom operators lies within its combination with existing Swift types. You can use extensions, generics, protocols and “where” to add custom types to existing types in Swift. Thanks to the flexibility of Swift’s type system, you can declare your custom operator once and use it with any or all types.
Check this out:
prefix operator ∑
extension Sequence where Self.Element: Numeric {
static prefix func ∑ (items: Self) -> Self.Element {
return items.reduce(0, { $0 + $1 })
}
}
The above code adds a prefix operator ∑
(Capital sigma) to the Sequence
type, via an extension. The operator produces the sum of the sequence’s members.
Using the where keyword, this extension only applies to sequences whose elements are numeric. You can use the ∑
operator on arrays, sets, sequences, ranges of integers, doubles, floats, etcetera.
∑(0...10)
// 55
∑[1, 2, 3]
// 6
∑[0.1, 3.9, 4.4, -2]
// 6.4
∑["Ford", "Trillian", "Arthur"].map { $0.count }
// 18
Awesome!
Quick Tip: You can also add existing operators to custom types in Swift. You can, for example, add the a + b
operator to your own struct, to be able to add 2 of the struct’s instances to each other! The code you need to do this is exactly the same as discussed in this section, except for the operator ···
declaration.
Further Reading
Pfew! Operators look so small but they can do so much. In essence, they’re tiny functions you can stick between a bunch of operands and voilá – you’ve got a result.
In this tutorial, we’ve discussed math-, assignment-, comparison-, range- and logical operators. We came across really cool operators like ??
and :?
. We’ve talked about operator precedence, associativity and grouping. And finally, we even built our own custom operator.
This tutorial pairs well with a few more in-depth guides:
- Ranges in Swift Explained
- Type Casting in Swift Explained
- Conditionals in Swift with If, Else If, Else
Want to learn more? Check out these resources: