Skip to content

Advanced Operators

Swift builds on basic operators with advanced ones for complex manipulations, like bitwise operations and custom definitions. Unlike C, Swift's arithmetic operators trap overflows as errors by default. To enable wrapping overflow, use prefixed versions like &+ for addition.

You can overload standard operators for custom types (structs, classes, enums) and even create new ones with custom precedence and associativity. This flexibility lets you tailor behavior precisely.

Bitwise operators manipulate individual bits, useful in low-level tasks like graphics or data encoding.

Bitwise Operators

Bitwise NOT (~)

Inverts all bits in a number. It's a prefix operator.

swift
let initialBits: UInt8 = 0b11000011  // Binary: 11000011 (decimal 195)
let invertedBits = ~initialBits      // Binary: 00111100 (decimal 60)

UInt8 holds values from 0 to 255. Here, initialBits flips to its inverse.

Bitwise AND (&)

Sets bits to 1 only where both inputs have 1.

swift
let leftBits: UInt8 = 0b11001100  // Binary: 11001100 (decimal 204)
let rightBits: UInt8 = 0b00110011 // Binary: 00110011 (decimal 51)
let resultBits = leftBits & rightBits  // Binary: 00000000 (decimal 0)

The overlapping 1s in the middle bits yield zeros elsewhere.

Bitwise OR (|)

Sets bits to 1 where either input has 1.

swift
let leftBits: UInt8 = 0b10010101  // Binary: 10010101 (decimal 149)
let rightBits: UInt8 = 0b01101010 // Binary: 01101010 (decimal 106)
let resultBits = leftBits | rightBits  // Binary: 11111111 (decimal 255)

Combines to fill all bits.

Bitwise XOR (^)

Sets bits to 1 where inputs differ.

swift
let leftBits: UInt8 = 0b10101010  // Binary: 10101010 (decimal 170)
let rightBits: UInt8 = 0b01010101 // Binary: 01010101 (decimal 85)
let resultBits = leftBits ^ rightBits  // Binary: 11111111 (decimal 255)

Differences toggle all bits to 1.

Bitwise Shifts (<< and >>)

Shift bits left (<<) or right (>>), effectively multiplying/dividing by powers of 2.

For unsigned integers (logical shift):

  • Bits shift as specified.
  • Overflow bits discard; zeros fill gaps.
swift
let baseBits: UInt8 = 8   // Binary: 00001000
baseBits << 1             // Binary: 00010000 (16)
baseBits << 3             // Binary: 10000000 (128)
baseBits >> 1             // Binary: 00000100 (4)
baseBits >> 4             // Binary: 00000000 (0)

Use for encoding, like extracting RGB from a hex color:

swift
let teal: UInt32 = 0x008080  // Hex for teal
let red = (teal & 0xFF0000) >> 16    // 0
let green = (teal & 0x00FF00) >> 8   // 128
let blue = teal & 0x0000FF            // 128

For signed integers (arithmetic shift):

  • Uses two's complement.
  • Right shifts fill with sign bit (preserves sign).

Shifting right moves toward zero while keeping the sign.

Overflow Operators

Swift prevents invalid values via errors. Opt into wrapping with &+, &-, &*.

Unsigned Overflow

swift
var unsignedWrap = UInt8.max  // 255
unsignedWrap = unsignedWrap &+ 1  // Wraps to 0

unsignedWrap = UInt8.min  // 0
unsignedWrap = unsignedWrap &- 1  // Wraps to 255

Positive overflow wraps from max to min; negative from min to max.

Signed Overflow

swift
var signedWrap = Int8.min  // -128
signedWrap = signedWrap &- 1  // Wraps to 127

Same wrapping logic, including sign bit.

Precedence and Associativity

Higher-precedence operators evaluate first. Same-precedence ones group by associativity (left or right).

Example: 10 - 2 * 3 + 4 evaluates as 10 - (2 * 3) + 4 = 8, since * precedes +/-, and all associate left-to-right.

Use parentheses for clarity. Swift's rules are simpler than C's—always verify when porting code.

See Swift's operator declarations for full precedence groups.

Operator Overloading

Add custom behavior to existing operators via static methods.

Binary Infix Example

Overload + for a 2D point struct:

swift
struct Point2D {
    var x = 0.0, y = 0.0
}

extension Point2D {
    static func + (left: Point2D, right: Point2D) -> Point2D {
        return Point2D(x: left.x + right.x, y: left.y + right.y)
    }
}

let p1 = Point2D(x: 1.0, y: 2.0)
let p2 = Point2D(x: 3.0, y: 4.0)
let sum = p1 + p2  // (4.0, 6.0)

Prefix/Postfix Unary

Overload unary - (prefix negation):

swift
extension Point2D {
    static prefix func - (point: Point2D) -> Point2D {
        return Point2D(x: -point.x, y: -point.y)
    }
}

let pos = Point2D(x: 5.0, y: 3.0)
let neg = -pos  // (-5.0, -3.0)

Postfix uses postfix modifier similarly.

Compound Assignment

Overload += (marks left as inout):

swift
extension Point2D {
    static func += (left: inout Point2D, right: Point2D) {
        left = left + right
    }
}

var origin = Point2D(x: 0.0, y: 0.0)
origin += p1  // (1.0, 2.0)

Can't overload plain =.

Equivalence (== and !=)

Conform to Equatable and implement ==; != is auto-negated.

swift
extension Point2D: Equatable {
    static func == (left: Point2D, right: Point2D) -> Bool {
        return left.x == right.x && left.y == right.y
    }
}

let a = Point2D(x: 1.0, y: 1.0)
let b = Point2D(x: 1.0, y: 1.0)
print(a == b)  // true

Synthesized implementations work for simple cases.

Custom Operators

Declare new ones globally with operator, specifying prefix, infix, or postfix.

Prefix Custom Example

swift
prefix operator **  // Custom "double" operator

extension Point2D {
    static prefix func ** (point: inout Point2D) -> Point2D {
        point += point
        return point
    }
}

var pt = Point2D(x: 2.0, y: 3.0)
let doubled = **pt  // pt and doubled now (4.0, 6.0)

Infix Custom with Precedence

Assign to a group like AdditionPrecedence:

swift
infix operator ~+: AdditionPrecedence  // x-plus-y variant

extension Point2D {
    static func ~+ (left: Point2D, right: Point2D) -> Point2D {
        return Point2D(x: left.x + right.x, y: left.y * right.y)
    }
}

let result = p1 ~+ p2  // (4.0, 8.0)

No precedence for prefix/postfix. Postfix applies before prefix on the same operand.

Result Builders

Result builders enable declarative syntax for building nested data (e.g., trees) using if, for, etc.

Define with @resultBuilder on a struct. Key methods: buildBlock for sequences, buildEither for if-else, buildArray for loops.

Drawing Example

Types for simple text drawings:

swift
protocol Renderable {
    func render() -> String
}

struct Row: Renderable {
    var parts: [Renderable]
    func render() -> String {
        return parts.map { $0.render() }.joined(separator: "")
    }
}

struct Label: Renderable {
    var text: String
    init(_ text: String) { self.text = text }
    func render() -> String { return text }
}

struct Gap: Renderable {
    func render() -> String { return " " }
}

struct Dashes: Renderable {
    var count: Int
    func render() -> String { return String(repeating: "-", count: count) }
}

struct Upper: Renderable {
    var inner: Renderable
    func render() -> String { return inner.render().uppercased() }
}

Manual build:

swift
let user = "Alex Kim"
let basicRow = Row(parts: [
    Dashes(count: 4),
    Label("Hi"),
    Gap(),
    Upper(inner: Label((user ?? "there") + ".")),
    Dashes(count: 3)
])
print(basicRow.render())  // "----HI ALEX KIM.---"

With builder:

swift
@resultBuilder
struct RowBuilder {
    static func buildBlock(_ items: Renderable...) -> Renderable {
        return Row(parts: items)
    }
    static func buildEither(first: Renderable) -> Renderable { first }
    static func buildEither(second: Renderable) -> Renderable { second }
    static func buildArray(_ items: [Renderable]) -> Renderable {
        return Row(parts: items)
    }
}

func layout(@RowBuilder body: () -> Renderable) -> Renderable { body() }
func bold(@RowBuilder body: () -> Renderable) -> Renderable {
    return Upper(inner: body())
}

func createMessage(for user: String? = nil) -> Renderable {
    return layout {
        Dashes(count: 4)
        Label("Hi")
        Gap()
        bold {
            if let user = user {
                Label(user + ".")
            } else {
                Label("there.")
            }
        }
        Dashes(count: 3)
    }
}

print(createMessage().render())  // "----HI THERE.---"
print(createMessage(for: "Alex Kim").render())  // "----HI ALEX KIM.---"

The builder transforms declarative code into method calls, simplifying conditionals and loops.

For full transformation details, see Swift's resultBuilder attribute.

Released under the MIT License.