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.
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.
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.
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.
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.
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:
let teal: UInt32 = 0x008080 // Hex for teal
let red = (teal & 0xFF0000) >> 16 // 0
let green = (teal & 0x00FF00) >> 8 // 128
let blue = teal & 0x0000FF // 128For 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
var unsignedWrap = UInt8.max // 255
unsignedWrap = unsignedWrap &+ 1 // Wraps to 0
unsignedWrap = UInt8.min // 0
unsignedWrap = unsignedWrap &- 1 // Wraps to 255Positive overflow wraps from max to min; negative from min to max.
Signed Overflow
var signedWrap = Int8.min // -128
signedWrap = signedWrap &- 1 // Wraps to 127Same 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:
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):
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):
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.
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) // trueSynthesized implementations work for simple cases.
Custom Operators
Declare new ones globally with operator, specifying prefix, infix, or postfix.
Prefix Custom Example
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:
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:
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:
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:
@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.