Skip to main content

Swift Custom Operators

Introduction

Swift provides a rich set of built-in operators for performing operations like addition (+), multiplication (*), comparison (==, <, >) and more. But what makes Swift particularly powerful is the ability to define your own custom operators to better express specific operations in your code.

Custom operators allow you to extend Swift's syntax to suit your application's domain-specific needs. Whether you're working with vectors, complex mathematical expressions, or domain-specific operations, custom operators can make your code more readable and expressive.

In this tutorial, we'll explore how to:

  • Define custom operators in Swift
  • Implement unary, binary, and ternary operators
  • Apply custom operators to practical scenarios
  • Follow best practices for creating intuitive operators

Understanding Operators in Swift

Before diving into custom operators, let's briefly review the types of operators in Swift:

  1. Unary operators: Act on a single target (e.g., -a, !b)

    • Prefix: appear before their target (-a)
    • Postfix: appear after their target (a!)
  2. Binary operators: Act on two targets (e.g., a + b, a * b)

    • Infix: appear between their targets
  3. Ternary operator: Acts on three targets (Swift has one: a ? b : c)

Creating Custom Operators

To create a custom operator in Swift, you need to follow two steps:

  1. Declare the operator and specify its properties
  2. Implement the operator functionality

Step 1: Declaring a Custom Operator

You declare a custom operator using one of these keywords:

  • prefix operator: For unary operators that come before their operand
  • postfix operator: For unary operators that come after their operand
  • infix operator: For binary operators that come between their operands

For infix operators, you can also specify precedence (the order in which operations are calculated) and associativity (how operators of the same precedence are grouped).

Step 2: Implementing the Operator

After declaring the operator, you implement it as a function using the same keyword:

  • Use prefix func for prefix operators
  • Use postfix func for postfix operators
  • Use infix func for infix operators

Example 1: Creating a Simple Custom Operator

Let's start by creating a simple exponentiation operator ** that raises one number to the power of another:

swift
// Declare the custom operator
infix operator **: MultiplicationPrecedence

// Implement the operator for Int values
func **(base: Int, power: Int) -> Int {
return Int(pow(Double(base), Double(power)))
}

// Usage
let result = 2 ** 3
print(result) // Output: 8

In this example, we:

  1. Declared an infix operator ** with precedence matching multiplication (so it's evaluated before addition but after parentheses)
  2. Implemented the operator for Int values
  3. Used the standard library's pow function to perform the calculation

Example 2: Custom Unary Operators

Let's create a custom prefix operator to negate a boolean:

swift
// Declare the prefix operator
prefix operator ¬

// Implement the operator
prefix func ¬(value: Bool) -> Bool {
return !value
}

// Usage
let isAvailable = true
let isUnavailable = ¬isAvailable
print(isUnavailable) // Output: false

Here we created a logical NOT operator using the mathematical notation symbol ¬.

Example 3: Vector Operations

Custom operators are particularly useful for mathematical operations. Let's implement vector addition using a custom operator:

swift
struct Vector2D {
var x: Double
var y: Double
}

// Define and implement a vector addition operator
infix operator +: AdditionPrecedence

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

// Usage
let v1 = Vector2D(x: 2.0, y: 3.0)
let v2 = Vector2D(x: 1.0, y: 4.0)
let v3 = v1 + v2
print("Vector sum: (\(v3.x), \(v3.y))") // Output: Vector sum: (3.0, 7.0)

Operator Precedence and Associativity

When creating custom operators, especially infix binary operators, it's important to consider precedence and associativity.

Precedence Groups

Swift has built-in precedence groups that determine the order of operations. From highest to lowest precedence:

  • BitwiseShiftPrecedence
  • MultiplicationPrecedence
  • AdditionPrecedence
  • ComparisonPrecedence
  • LogicalConjunctionPrecedence
  • LogicalDisjunctionPrecedence
  • TernaryPrecedence
  • AssignmentPrecedence

You can also create your own precedence group:

swift
precedencegroup ExponentiationPrecedence {
higherThan: MultiplicationPrecedence
lowerThan: BitwiseShiftPrecedence
associativity: right
}

infix operator **: ExponentiationPrecedence

// Now the exponentiation operator has higher precedence than multiplication

Associativity

Associativity determines how operators with the same precedence are grouped:

  • left: Operations are grouped from left to right (e.g., a - b - c is interpreted as (a - b) - c)
  • right: Operations are grouped from right to left (e.g., a = b = c is interpreted as a = (b = c))
  • none: Operations cannot be chained (e.g., a < b < c is invalid)

Practical Applications

Example 4: Date Manipulation

Let's create an operator to add days to a date:

swift
infix operator +~: AdditionPrecedence

func +~(lhs: Date, rhs: Int) -> Date {
return Calendar.current.date(byAdding: .day, value: rhs, to: lhs)!
}

// Usage
let today = Date()
let nextWeek = today +~ 7
print("Today: \(today)")
print("Next week: \(nextWeek)")

Example 5: Option Unwrapping with Default

Let's create an operator that unwraps an optional or returns a default value:

swift
infix operator ??: NilCoalescingPrecedence

func ??<T>(optional: T?, defaultValue: @autoclosure () -> T) -> T {
return optional ?? defaultValue()
}

// Usage
let possibleValue: Int? = nil
let definiteValue = possibleValue ?? 42
print(definiteValue) // Output: 42

Wait, this looks like Swift's built-in nil coalescing operator! That's because it is—Swift actually implements its nil coalescing operator as a custom operator.

Best Practices for Custom Operators

  1. Use sparingly: Custom operators can make code less readable if overused.

  2. Be intuitive: Choose symbols that suggest the operation (e.g., + for addition, * for multiplication).

  3. Follow conventions: If possible, use symbols that align with established mathematical or programming conventions.

  4. Document well: Always document what your custom operators do and how they should be used.

  5. Consider alternatives: Sometimes a well-named function is more readable than a custom operator.

  6. Be consistent: Your operators should behave consistently with similar operations.

Common Symbols for Custom Operators

Swift allows you to use these symbols for custom operators:

/ = - + * % < > ! & | ^ ~ .

You can also use Unicode mathematical and symbol characters like:

√ ∑ ∆ ⊕ ⊗ ¬

Summary

Custom operators in Swift provide a powerful way to extend the language to better fit your specific domain needs. By creating well-designed operators, you can make your code more expressive, concise, and readable. However, with great power comes great responsibility—custom operators should be used judiciously and with clear documentation.

When creating custom operators, always consider:

  • The operator's purpose and semantics
  • Appropriate precedence and associativity
  • The intuitive meaning of the chosen symbol
  • Whether a well-named function might be clearer

Exercises

  1. Create a custom operator +++ that triples a number.
  2. Implement a custom operator for string repetition (e.g., "a" * 3 = "aaa").
  3. Create a vector cross product operator for Vector2D named .
  4. Implement a custom operator to merge two dictionaries.
  5. Create a custom postfix operator !! that forcefully unwraps an optional but prints a warning message if the value is nil.

Additional Resources



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)