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/FixedWidthInteger or any other protocol.

  • Protocols — Equatable and the other protocols an operator implementation typically conforms to.