Advanced Operators
|
This section documents the Swift 6 language mode as shipped by Swift 6.3, as published in The Swift Programming Language at docs.swift.org, which is the reference these pages are written and verified against. 6.4-beta-only features are always flagged as such — never presented as baseline. This content was generated with the assistance of AI and should be verified against docs.swift.org before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
Beyond the arithmetic and comparison operators covered in Operators, Swift exposes the bit-level operators most C-family languages provide, lets any type define its own operators (including entirely new ones), and gives the compiler a precise model of precedence and associativity so expressions involving them parse the way a reader expects.
Bits and Bytes
Swift’s integer types (UInt8, Int32, UInt, …) store values as a fixed-width sequence of bits, most
significant bit first. Endianness — whether the most significant byte of a multi-byte value is stored first
(big-endian) or last (little-endian) — is a property of how a platform lays bytes out in memory, not of the
Int/UInt values themselves: Swift’s integer operators always work on the numeric value, and only code that
reads or writes raw bytes (e.g. via withUnsafeBytes, network protocols, or file formats) needs to reason about
byte order explicitly, typically with bigEndian/littleEndian/byteSwapped.
let value: UInt32 = 0x1234_5678
value.bigEndian // the same bits laid out most-significant-byte-first
value.littleEndian // the same bits laid out least-significant-byte-first
value.byteSwapped // reverses the byte order regardless of the platform's native endianness
Bitwise Operators
let initialBits: UInt8 = 0b0000_1111
let invertedBits = ~initialBits // bitwise NOT: 0b1111_0000
let firstSixBits: UInt8 = 0b1111_1100
let lastSixBits: UInt8 = 0b0011_1111
let combinedbits = firstSixBits & lastSixBits // bitwise AND: 0b0011_1100 -- 1 only where both operands are 1
let outputBits = firstSixBits | lastSixBits // bitwise OR: 0b1111_1111 -- 1 where either operand is 1
let firstBits: UInt8 = 0b0001_0110
let otherBits: UInt8 = 0b0000_1010
let outputXorBits = firstBits ^ otherBits // bitwise XOR: 0b0001_1100 -- 1 where exactly one operand is 1
let shiftBits: UInt8 = 4 // 0b0000_0100
shiftBits << 1 // 0b0000_1000 == 8 -- unsigned left shift: multiply by 2 per position
shiftBits >> 2 // 0b0000_0001 == 1 -- unsigned right shift: divide by 2 per position
let signedShift: Int8 = -8 // stored as two's complement
signedShift >> 1 // arithmetic right shift: fills with the sign bit, preserving sign
Unsigned shifts (<</>> on UInt*) are logical shifts: vacated bits are always filled with 0. Signed
shifts are arithmetic: a right shift fills vacated bits with a copy of the sign bit instead, so shifting a
negative value right keeps it negative — the two’s-complement encoding that makes this work is the same one
~ (bitwise NOT) relies on to compute a negative value’s representation.
Overflow Operators
var unsignedOverflow = UInt8.max // 255
// unsignedOverflow += 1 // would trap: "Execution was interrupted, reason: EXC_BAD_INSTRUCTION"
unsignedOverflow = unsignedOverflow &+ 1 // wraps around to 0 instead of trapping
var unsignedUnderflow = UInt8.min // 0
unsignedUnderflow = unsignedUnderflow &- 1 // wraps around to 255
var signedOverflow = Int8.min // -128
signedOverflow = signedOverflow &- 1 // wraps around to 127
let x = Int8.max
let (result, overflowed) = x.addingReportingOverflow(1) // result: -128 (wrapped), overflowed: true
Swift’s ordinary arithmetic operators (`, `-`, `*`) deliberately *trap* on overflow rather than silently
wrapping, catching a whole class of bugs C-family languages let through unnoticed. The three overflow operators
`&, &- and &* opt back into C-style wraparound behavior for the rare cases that genuinely want it (hash
mixing, checksum-style arithmetic, emulating fixed-width hardware registers). addingReportingOverflow(_:) and
its subtracting/multiplying/dividing siblings go further, returning both the wrapped result and a
Bool saying whether wraparound actually happened, so a caller can detect and handle the overflow explicitly
instead of choosing between trapping and silently wrapping.
Precedence and Associativity
2 + 3 % 4 * 5 // % and * bind tighter than + (MultiplicationPrecedence > AdditionPrecedence): 2 + ((3 % 4) * 5) == 17
2 - 3 - 4 // - is left-associative: (2 - 3) - 4 == -5, not 2 - (3 - 4)
Every operator belongs to a precedence group (AdditionPrecedence, MultiplicationPrecedence,
ComparisonPrecedence, …) that fixes both how tightly it binds relative to other groups and its
associativity — left, right, or none (an operator with none associativity cannot be chained without
explicit parentheses at all).
Operator Methods on Custom Types
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)
}
static prefix func - (vector: Vector2D) -> Vector2D {
Vector2D(x: -vector.x, y: -vector.y)
}
static func += (left: inout Vector2D, right: Vector2D) {
left = left + right // compound assignment implemented in terms of the plain operator
}
static prefix func ++ (vector: inout Vector2D) -> Vector2D { // custom prefix operator, declared below
vector += Vector2D(x: 1.0, y: 1.0)
return vector
}
}
extension Vector2D: Equatable {
static func == (left: Vector2D, right: Vector2D) -> Bool {
left.x == right.x && left.y == right.y
}
}
Operators are declared as static methods on the type (or as protocol requirements, as Equatable’s `== is):
`/`-`/`*`/`/` as binary infix methods, unary minus as a `static prefix func`, and compound assignment operators
(`=) taking their left-hand side as inout. Conforming to Equatable and implementing == is by far the most
common case — and, for a struct whose stored properties are all themselves Equatable, the compiler can
synthesize it automatically without writing == at all.
Custom Operators and Precedence Groups
prefix operator +++
extension Vector2D {
static prefix func +++ (vector: inout Vector2D) -> Vector2D {
vector += vector
return vector
}
}
infix operator +-: AdditionPrecedence // slots +- into an existing precedence group
precedencegroup ExponentiationPrecedence {
associativity: left
higherThan: MultiplicationPrecedence
}
infix operator **: ExponentiationPrecedence
func ** (base: Double, power: Int) -> Double {
var result = 1.0
for _ in 0..<power { result *= base }
return result
}
2.0 ** 3 * 2.0 // ** binds tighter than *: (2.0 ** 3) * 2.0 == 16.0
A brand-new operator is declared at file scope with prefix operator, postfix operator, or infix operator,
the last naming an existing (AdditionPrecedence) or custom precedencegroup it belongs to. A custom
precedencegroup states its own associativity and its relation (higherThan/lowerThan) to other groups,
giving a new operator family the same well-defined parsing behavior as the built-in ones.
BinaryInteger and FixedWidthInteger
func isPowerOfTwo<T: FixedWidthInteger>(_ value: T) -> Bool {
value > 0 && value & (value - 1) == 0 // generic over any fixed-width integer type, signed or unsigned
}
isPowerOfTwo(64) // true
isPowerOfTwo(UInt8(48)) // false
func leadingZeroBits<T: FixedWidthInteger>(_ value: T) -> Int {
value.leadingZeroBitCount
}
Writing bit-manipulation code generically — rather than once per concrete integer type — means constraining a
generic parameter to BinaryInteger (the protocol behind &, |, ^, ~, and the shift operators) or its
refinement FixedWidthInteger (which additionally guarantees a fixed bitWidth, leadingZeroBitCount, the
overflow-reporting operations, and min/max bounds) — see
Generics for constraining type parameters to protocols in
general.
See Also
-
Operators — the arithmetic, comparison, logical, range and optional operators these advanced ones build on.
-
Basics: Constants, Variables and Types — the integer and floating-point types these operators act on.
-
Generics — constraining a type parameter to
BinaryInteger/FixedWidthIntegeror any other protocol. -
Protocols —
Equatableand the other protocols an operator implementation typically conforms to.