Advanced Operators
Beyond the basic operators, Swift provides advanced operators for low-level bit manipulation, controlled overflow arithmetic, and — most powerfully — the ability to define your own operators and overload existing ones for your own types. This chapter also introduces result builders, which use operator-like composition to build values from nested expressions.
Note: unlike arithmetic operators in C, Swift's arithmetic operators don't overflow by default. Overflow is trapped and reported as an error. To opt into overflow behavior, you use the dedicated overflow operators described below.
Bitwise operators
Bitwise operators let you manipulate the individual raw data bits within a data structure. Swift supports the five standard bitwise operators from C.
The bitwise NOT operator (~) is a prefix
operator that inverts all bits in a number:
let initialBits: UInt8 = 0b00001111
let invertedBits = ~initialBits // 0b11110000 → 240
~ flips every bit.
The bitwise AND (&) keeps a bit only where
both inputs have it set; OR (|) sets a bit where
either input has it; XOR (^) sets a bit where
exactly one input has it:
let a: UInt8 = 0b11111100
let b: UInt8 = 0b00111111
let and = a & b // 0b00111100 → 60
let or = a | b // 0b11111111 → 255
let xor = a ^ b // 0b11000011 → 195
Bitwise AND, OR, and XOR combine two values bit by bit.
The shift operators (<< and
>>) move all of a number's bits left or right by a number
of positions. For unsigned integers, shifting is logical: bits shifted off
the end are discarded, and zeros are inserted on the other side. For signed
integers, the right shift is arithmetic — it fills with copies of
the sign bit to preserve the number's sign.
let pink: UInt32 = 0xCC6699
let red = (pink & 0xFF0000) >> 16 // 0xCC → 204
let green = (pink & 0x00FF00) >> 8 // 0x66 → 102
let blue = pink & 0x0000FF // 0x99 → 153
Masking and shifting to extract color channels from a hex value.
Overflow operators
If you try to insert a value into an integer that can't hold it, Swift
reports an error rather than wrapping around. When you specifically
want the value to truncate, opt in with the three overflow
operators: overflow addition (&+), overflow subtraction
(&-), and overflow multiplication (&*).
var unsignedOverflow = UInt8.max // 255, the largest UInt8
unsignedOverflow = unsignedOverflow &+ 1 // wraps to 0
var low = UInt8.min // 0, the smallest UInt8
low = low &- 1 // wraps to 255
// Without &, `UInt8.max + 1` traps at runtime.
Overflow operators wrap around the value's bounds instead of trapping.
Precedence and associativity
Operator precedence gives some operators higher priority, so they're applied first. Associativity defines how operators of the same precedence are grouped — either from the left or from the right. Swift's multiplication and division operators have higher precedence than addition and subtraction, and within the same group they're left-associative:
let result = 2 + 3 % 4 * 5
// % and * bind tighter than +, and group left-to-right:
// 2 + ((3 % 4) * 5) = 2 + (3 * 5) = 17
Precedence and associativity determine grouping without parentheses.
Tip: Swift's precedence rules are simpler and more predictable than C's, but they may differ from languages you know. When in doubt, add parentheses to make your intent explicit and readable.
Operator methods
Classes and structures can provide their own implementations of existing
operators — this is called overloading. You implement an
operator as a type method (typically static):
struct Vector2D {
var x = 0.0, y = 0.0
}
extension Vector2D {
static func + (left: Vector2D, right: Vector2D) -> Vector2D {
Vector2D(x: left.x + right.x, y: left.y + right.y)
}
}
let combined = Vector2D(x: 3, y: 1) + Vector2D(x: 2, y: 4)
// combined is (x: 5, y: 5)
An infix + operator method for a custom type.
Prefix and postfix operators
Operators that take a single operand are unary. You declare
them with the prefix or postfix modifier to say
which side the operand sits on:
extension Vector2D {
static prefix func - (vector: Vector2D) -> Vector2D {
Vector2D(x: -vector.x, y: -vector.y)
}
}
let positive = Vector2D(x: 3, y: 4)
let negative = -positive // (x: -3, y: -4)
A unary prefix minus that negates both components.
Compound assignment operators
Compound assignment operators combine assignment (=) with
another operation. You mark the left input as inout, because its
value is modified directly inside the method:
extension Vector2D {
static func += (left: inout Vector2D, right: Vector2D) {
left = left + right
}
}
var original = Vector2D(x: 1, y: 2)
original += Vector2D(x: 3, y: 4) // original is now (4, 6)
+= takes its left operand inout so it can be mutated in place.
Watch out: you can't overload the default assignment
operator (=) itself, nor the ternary conditional operator
(a ? b : c). These are reserved by the language.
Equivalence operators
To support == and !=, conform to the
Equatable protocol. For most types Swift can synthesize the
conformance automatically, but you can implement == yourself
when you need custom logic (Swift derives != for you):
extension Vector2D: Equatable {
static func == (left: Vector2D, right: Vector2D) -> Bool {
left.x == right.x && left.y == right.y
}
}
let same = Vector2D(x: 1, y: 2) == Vector2D(x: 1, y: 2) // true
Conforming to Equatable with a custom == operator method.
Custom operators
You can declare and implement entirely new operators in addition to the
standard ones. New operators are declared at global scope using the
operator keyword, marked prefix,
infix, or postfix:
prefix operator +++
extension Vector2D {
static prefix func +++ (vector: inout Vector2D) -> Vector2D {
vector += vector // a "doubling" operator
return vector
}
}
var toDouble = Vector2D(x: 1, y: 4)
let doubled = +++toDouble // both are now (2, 8)
Declaring and defining a brand-new prefix operator.
Precedence groups
When you define a custom infix operator, it belongs to a
precedence group that tells Swift how it relates to other
infix operators in precedence and associativity. If you don't specify one, it
joins the default group, which has higher precedence than the ternary
operator. You define your own group with the precedencegroup
keyword:
precedencegroup VectorAdditionPrecedence {
associativity: left
higherThan: AdditionPrecedence
}
infix operator +-: VectorAdditionPrecedence
extension Vector2D {
static func +- (left: Vector2D, right: Vector2D) -> Vector2D {
Vector2D(x: left.x + right.x, y: left.y - right.y)
}
}
let v = Vector2D(x: 1, y: 2) +- Vector2D(x: 3, y: 4) // (4, -2)
A precedence group sets associativity and how the operator ranks against others.
Each group can declare it's higherThan or lowerThan
existing groups and choose left, right, or
none associativity. You don't specify a precedence group for
prefix or postfix operators — if you apply both a prefix and a postfix
operator to the same value, the postfix one is applied first.
Result builders
A result builder is a type you define that adds syntax for
creating nested data — like a tree or list — in a natural, declarative way.
Result builders are how SwiftUI lets you list views inside a
VStack without commas or array brackets. You create one by
applying the @resultBuilder attribute to a type and providing
buildBlock (and optionally buildOptional,
buildEither, buildArray, and others):
@resultBuilder
struct StringBuilder {
static func buildBlock(_ parts: String...) -> String {
parts.joined(separator: "\n")
}
static func buildOptional(_ part: String?) -> String {
part ?? ""
}
static func buildEither(first: String) -> String { first }
static func buildEither(second: String) -> String { second }
}
A minimal result builder that joins strings, with support for if/else.
Apply the builder as a custom attribute on a function or closure parameter.
Inside that scope, the builder transforms each statement into calls to its
build methods, so you can use if, if let, and
loops naturally:
func makeGreeting(@StringBuilder _ content: () -> String) -> String {
content()
}
let excited = true
let message = makeGreeting {
"Hello"
if excited {
"Welcome aboard!"
} else {
"Glad you're here."
}
"— The Team"
}
// → "Hello\nWelcome aboard!\n— The Team"
The builder turns plain statements (including control flow) into a single result.
Tip: you've already been using a result builder.
SwiftUI's @ViewBuilder is exactly this mechanism — it's what lets
the contents of a stack or list read like a simple sequence of views.
Wrapping up
Advanced operators take you from manipulating raw bits to shaping the language itself. Bitwise and overflow operators handle low-level numeric work; operator methods and custom operators let your own types read as naturally as built-in ones; precedence groups control how expressions combine; and result builders turn nested statements into rich values — the foundation of declarative APIs like SwiftUI.